diff options
Diffstat (limited to 'src/network/ssl/qdtls.cpp')
-rw-r--r-- | src/network/ssl/qdtls.cpp | 1163 |
1 files changed, 1163 insertions, 0 deletions
diff --git a/src/network/ssl/qdtls.cpp b/src/network/ssl/qdtls.cpp new file mode 100644 index 0000000000..bbb22aa527 --- /dev/null +++ b/src/network/ssl/qdtls.cpp @@ -0,0 +1,1163 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qsslconfiguration.h" +#include "qdtls_openssl_p.h" +#include "qudpsocket.h" +#include "qdtls_p.h" +#include "qssl_p.h" +#include "qdtls.h" + +#include "qglobal.h" + +/*! + \class QDtlsClientVerifier + \brief This class implements server-side DTLS cookie generation and verification. + \since 5.12 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + The QDtlsClientVerifier class implements server-side DTLS cookie generation + and verification. Datagram security protocols are highly susceptible to a + variety of Denial-of-Service attacks. According to \l {https://tools.ietf.org/html/rfc6347#section-4.2.1}{RFC 6347, section 4.2.1}, + these are two of the more common types of attack: + + \list + \li An attacker transmits a series of handshake initiation requests, causing + a server to allocate excessive resources and potentially perform expensive + cryptographic operations. + \li An attacker transmits a series of handshake initiation requests with + a forged source of the victim, making the server act as an amplifier. + Normally, the server would reply to the victim machine with a Certificate message, + which can be quite large, thus flooding the victim machine with datagrams. + \endlist + + As a countermeasure to these attacks, \l {https://tools.ietf.org/html/rfc6347#section-4.2.1}{RFC 6347, section 4.2.1} + proposes a stateless cookie technique that a server may deploy: + + \list + \li In response to the initial ClientHello message, the server sends a HelloVerifyRequest, + which contains a cookie. This cookie is a cryptographic hash and is generated using the + client's address, port number, and the server's secret (which is a cryptographically strong + pseudo-random sequence of bytes). + \li A reachable DTLS client is expected to reply with a new ClientHello message + containing this cookie. + \li When the server receives the ClientHello message with a cookie, it + generates a new cookie as described above. This new cookie is compared to the + one found in the ClientHello message. + \li In the cookies are equal, the client is considered to be real, and the + server can continue with a TLS handshake procedure. + \endlist + + \note A DTLS server is not required to use DTLS cookies. + + QDtlsClientVerifier is designed to work in pair with QUdpSocket, as shown in + the following code-excerpt: + + \snippet code/src_network_ssl_qdtlscookie.cpp 0 + + QDtlsClientVerifier does not impose any restrictions on how the application uses + QUdpSocket. For example, it is possible to have a server with a single QUdpSocket + in state QAbstractSocket::BoundState, handling multiple DTLS clients + simultaneously: + + \list + \li Testing if new clients are real DTLS-capable clients. + \li Completing TLS handshakes with the verified clients (see QDtls). + \li Decrypting datagrams coming from the connected clients (see QDtls). + \li Sending encrypted datagrams to the connected clients (see QDtls). + \endlist + + This implies that QDtlsClientVerifier does not read directly from a socket, + instead it expects the application to read an incoming datagram, extract the + sender's address, and port, and then pass this data to verifyClient(). + To send a HelloVerifyRequest message, verifyClient() can write to the QUdpSocket. + + \note QDtlsClientVerifier does not take ownership of the QUdpSocket object. + + By default QDtlsClientVerifier obtains its secret from a cryptographically + strong pseudorandom number generator. + + \note The default secret is shared by all objects of the classes QDtlsClientVerifier + and QDtls. Since this can impose security risks, RFC 6347 recommends to change + the server's secret frequently. Please see \l {https://tools.ietf.org/html/rfc6347}{RFC 6347, section 4.2.1} + for hints about possible server implementations. Cookie generator parameters + can be set using the class QDtlsClientVerifier::GeneratorParameters and + setCookieGeneratorParameters(): + + \snippet code/src_network_ssl_qdtlscookie.cpp 1 + + The \l{secureudpserver}{DTLS server} example illustrates how to use + QDtlsClientVerifier in a server application. + + \sa QUdpSocket, QAbstractSocket::BoundState, QDtls, verifyClient(), + GeneratorParameters, setCookieGeneratorParameters(), cookieGeneratorParameters(), + QDtls::setCookieGeneratorParameters(), + QDtls::cookieGeneratorParameters(), + QCryptographicHash::Algorithm, + QDtlsError, dtlsError(), dtlsErrorString() +*/ + +/*! + \class QDtlsClientVerifier::GeneratorParameters + \brief This class defines parameters for DTLS cookie generator. + \since 5.12 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + An object of this class provides the parameters that QDtlsClientVerifier + will use to generate DTLS cookies. They include a cryptographic hash + algorithm and a secret. + + \note An empty secret is considered to be invalid by + QDtlsClientVerifier::setCookieGeneratorParameters(). + + \sa QDtlsClientVerifier::setCookieGeneratorParameters(), + QDtlsClientVerifier::cookieGeneratorParameters(), + QDtls::setCookieGeneratorParameters(), + QDtls::cookieGeneratorParameters(), + QCryptographicHash::Algorithm +*/ + +/*! + \enum QDtlsError + \brief Describes errors that can be found by QDtls and QDtlsClientVerifier. + \relates QDtls + \since 5.12 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + This enum describes general and TLS-specific errors that can be encountered + by objects of the classes QDtlsClientVerifier and QDtls. + + \value NoError No error occurred, the last operation was successful. + \value InvalidInputParameters Input parameters provided by a caller were + invalid. + \value InvalidOperation An operation was attempted in a state that did not + permit it. + \value UnderlyingSocketError QUdpSocket::writeDatagram() failed, QUdpSocket::error() + and QUdpSocket::errorString() can provide more specific information. + \value RemoteClosedConnectionError TLS shutdown alert message was received. + \value PeerVerificationError Peer's identity could not be verified during the + TLS handshake. + \value TlsInitializationError An error occurred while initializing an underlying + TLS backend. + \value TlsFatalError A fatal error occurred during TLS handshake, other + than peer verification error or TLS initialization error. + \value TlsNonFatalError A failure to encrypt or decrypt a datagram, non-fatal, + meaning QDtls can continue working after this error. +*/ + +/*! + \class QDtls + \brief This class provides encryption for UDP sockets. + \since 5.12 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + The QDtls class can be used to establish a secure connection with a network + peer using User Datagram Protocol (UDP). DTLS connection over essentially + connectionless UDP means that two peers first have to successfully complete + a TLS handshake by calling doHandshake(). After the handshake has completed, + encrypted datagrams can be sent to the peer using writeDatagramEncrypted(). + Encrypted datagrams coming from the peer can be decrypted by decryptDatagram(). + + QDtls is designed to work with QUdpSocket. Since QUdpSocket can receive + datagrams coming from different peers, an application must implement + demultiplexing, forwarding datagrams coming from different peers to their + corresponding instances of QDtls. An association between a network peer + and its QDtls object can be established using the peer's address and port + number. Before starting a handshake, the application must set the peer's + address and port number using setPeer(). + + QDtls does not read datagrams from QUdpSocket, this is expected to be done by + the application, for example, in a slot attached to the QUdpSocket::readyRead() + signal. Then, these datagrams must be processed by QDtls. + + \note QDtls does \e not take ownership of the QUdpSocket object. + + Normally, several datagrams are to be received and sent by both peers during + the handshake phase. Upon reading datagrams, server and client must pass these + datagrams to doHandshake() until some error is found or handshakeState() + returns HandshakeComplete: + + \snippet code/src_network_ssl_qdtls.cpp 0 + + For a server, the first call to doHandshake() requires a non-empty datagram + containing a ClientHello message. If the server also deploys QDtlsClientVerifier, + the first ClientHello message is expected to be the one verified by QDtlsClientVerifier. + + In case the peer's identity cannot be validated during the handshake, the application + must inspect errors returned by peerVerificationErrors() and then either + ignore errors by calling ignoreVerificationErrors() or abort the handshake + by calling abortHandshake(). If errors were ignored, the handshake can be + resumed by calling resumeHandshake(). + + After the handshake has been completed, datagrams can be sent to and received + from the network peer securely: + + \snippet code/src_network_ssl_qdtls.cpp 2 + + A DTLS connection may be closed using shutdown(). + + \snippet code/src_network_ssl_qdtls.cpp 3 + + \warning It's recommended to call shutdown() before destroying the client's QDtls + object if you are planning to re-use the same port number to connect to the + server later. Otherwise, the server may drop incoming ClientHello messages, + see \l{https://tools.ietf.org/html/rfc6347#page-25}{RFC 6347, section 4.2.8} + for more details and implementation hints. + + If the server does not use QDtlsClientVerifier, it \e must configure its + QDtls objects to disable the cookie verification procedure: + + \snippet code/src_network_ssl_qdtls.cpp 4 + + A server that uses cookie verification with non-default generator parameters + \e must set the same parameters for its QDtls object before starting the handshake. + + \note The DTLS protocol leaves Path Maximum Transmission Unit (PMTU) discovery + to the application. The application may provide QDtls with the MTU using + setMtuHint(). This hint affects only the handshake phase, since only handshake + messages can be fragmented and reassembled by the DTLS. All other messages sent + by the application must fit into a single datagram. + \note DTLS-specific headers add some overhead to application data further + reducing the possible message size. + \warning A server configured to reply with HelloVerifyRequest will drop + all fragmented ClientHello messages, never starting a handshake. + + The \l{secureudpserver}{DTLS server} and \l{secureudpclient}{DTLS client} + examples illustrate how to use QDtls in applications. + + \sa QUdpSocket, QDtlsClientVerifier, HandshakeState, QDtlsError, QSslConfiguration +*/ + +/*! + \typedef QDtls::GeneratorParameters + + This is a synonym for QDtlsClientVerifier::GeneratorParameters. +*/ + +/*! + \fn void QDtls::handshakeTimeout() + + Packet loss can result in timeouts during the handshake phase. In this case + QDtls emits a handshakeTimeout() signal. Call handleTimeout() to retransmit + the handshake messages: + + \snippet code/src_network_ssl_qdtls.cpp 1 + + \sa handleTimeout() +*/ + +/*! + \fn void QDtls::pskRequired(QSslPreSharedKeyAuthenticator *authenticator) + + QDtls emits this signal when it negotiates a PSK ciphersuite, and therefore + a PSK authentication is then required. + + When using PSK, the client must send to the server a valid identity and a + valid pre shared key, in order for the TLS handshake to continue. + Applications can provide this information in a slot connected to this + signal, by filling in the passed \a authenticator object according to their + needs. + + \note Ignoring this signal, or failing to provide the required credentials, + will cause the handshake to fail, and therefore the connection to be aborted. + + \note The \a authenticator object is owned by QDtls and must not be deleted + by the application. + + \sa QSslPreSharedKeyAuthenticator +*/ + +/*! + \enum QDtls::HandshakeState + \brief Describes the current state of DTLS handshake. + \since 5.12 + + \ingroup network + \ingroup ssl + \inmodule QtNetwork + + This enum describes the current state of DTLS handshake for a QDtls + connection. + + \value HandshakeNotStarted Nothing done yet. + \value HandshakeInProgress Handshake was initiated and no errors were found so far. + \value PeerVerificationFailed The identity of the peer can't be established. + \value HandshakeComplete Handshake completed successfully and encrypted connection + was established. + + \sa QDtls::doHandshake(), QDtls::handshakeState() +*/ + + +QT_BEGIN_NAMESPACE + +QSslConfiguration QDtlsBasePrivate::configuration() const +{ + auto copyPrivate = new QSslConfigurationPrivate(dtlsConfiguration); + copyPrivate->ref.store(0); // the QSslConfiguration constructor refs up + QSslConfiguration copy(copyPrivate); + copyPrivate->sessionCipher = sessionCipher; + copyPrivate->sessionProtocol = sessionProtocol; + + return copy; +} + +void QDtlsBasePrivate::setConfiguration(const QSslConfiguration &configuration) +{ + dtlsConfiguration.localCertificateChain = configuration.localCertificateChain(); + dtlsConfiguration.privateKey = configuration.privateKey(); + dtlsConfiguration.ciphers = configuration.ciphers(); + dtlsConfiguration.ellipticCurves = configuration.ellipticCurves(); + dtlsConfiguration.preSharedKeyIdentityHint = configuration.preSharedKeyIdentityHint(); + dtlsConfiguration.dhParams = configuration.diffieHellmanParameters(); + dtlsConfiguration.caCertificates = configuration.caCertificates(); + dtlsConfiguration.peerVerifyDepth = configuration.peerVerifyDepth(); + dtlsConfiguration.peerVerifyMode = configuration.peerVerifyMode(); + dtlsConfiguration.protocol = configuration.protocol(); + dtlsConfiguration.sslOptions = configuration.d->sslOptions; + dtlsConfiguration.sslSession = configuration.sessionTicket(); + dtlsConfiguration.sslSessionTicketLifeTimeHint = configuration.sessionTicketLifeTimeHint(); + dtlsConfiguration.nextAllowedProtocols = configuration.allowedNextProtocols(); + dtlsConfiguration.nextNegotiatedProtocol = configuration.nextNegotiatedProtocol(); + dtlsConfiguration.nextProtocolNegotiationStatus = configuration.nextProtocolNegotiationStatus(); + dtlsConfiguration.dtlsCookieEnabled = configuration.dtlsCookieVerificationEnabled(); + dtlsConfiguration.allowRootCertOnDemandLoading = configuration.d->allowRootCertOnDemandLoading; + + clearDtlsError(); +} + +bool QDtlsBasePrivate::setCookieGeneratorParameters(QCryptographicHash::Algorithm alg, + const QByteArray &key) +{ + if (!key.size()) { + setDtlsError(QDtlsError::InvalidInputParameters, + QDtls::tr("Invalid (empty) secret")); + return false; + } + + clearDtlsError(); + + hashAlgorithm = alg; + secret = key; + + return true; +} + +bool QDtlsBasePrivate::isDtlsProtocol(QSsl::SslProtocol protocol) +{ + switch (protocol) { + case QSsl::DtlsV1_0: + case QSsl::DtlsV1_0OrLater: + case QSsl::DtlsV1_2: + case QSsl::DtlsV1_2OrLater: + return true; + default: + return false; + } +} + +static QString msgUnsupportedMulticastAddress() +{ + return QDtls::tr("Multicast and broadcast addresses are not supported"); +} + +/*! + Default constructs GeneratorParameters object with QCryptographicHash::Sha1 + as its algorithm and an empty secret. + + \sa QDtlsClientVerifier::setCookieGeneratorParameters(), + QDtlsClientVerifier::cookieGeneratorParameters(), + QDtls::setCookieGeneratorParameters(), + QDtls::cookieGeneratorParameters() + */ +QDtlsClientVerifier::GeneratorParameters::GeneratorParameters() +{ +} + +/*! + Constructs GeneratorParameters object from \a algorithm and \a secret. + + \sa QDtlsClientVerifier::setCookieGeneratorParameters(), + QDtlsClientVerifier::cookieGeneratorParameters(), + QDtls::setCookieGeneratorParameters(), + QDtls::cookieGeneratorParameters() + */ +QDtlsClientVerifier::GeneratorParameters::GeneratorParameters(QCryptographicHash::Algorithm algorithm, const QByteArray &secret) + : hash(algorithm), secret(secret) +{ +} + +/*! + Constructs a QDtlsClientVerifier object, \a parent is passed to QObject's + constructor. +*/ +QDtlsClientVerifier::QDtlsClientVerifier(QObject *parent) + : QObject(*new QDtlsClientVerifierOpenSSL, parent) +{ + Q_D(QDtlsClientVerifier); + + d->mode = QSslSocket::SslServerMode; + // The default configuration suffices: verifier never does a full + // handshake and upon verifying a cookie in a client hello message, + // it reports success. + auto conf = QSslConfiguration::defaultDtlsConfiguration(); + conf.setPeerVerifyMode(QSslSocket::VerifyNone); + d->setConfiguration(conf); +} + +/*! + Destroys the QDtlsClientVerifier object. +*/ +QDtlsClientVerifier::~QDtlsClientVerifier() +{ +} + +/*! + Sets the secret and the cryptographic hash algorithm from \a params. This + QDtlsClientVerifier will use these to generate cookies. If the new secret + has size zero, this function returns \c false and does not change the + cookie generator parameters. + + \note The secret is supposed to be a cryptographically secure sequence of bytes. + + \sa QDtlsClientVerifier::GeneratorParameters, cookieGeneratorParameters(), + QCryptographicHash::Algorithm +*/ +bool QDtlsClientVerifier::setCookieGeneratorParameters(const GeneratorParameters ¶ms) +{ + Q_D(QDtlsClientVerifier); + + return d->setCookieGeneratorParameters(params.hash, params.secret); +} + +/*! + Returns the current secret and hash algorithm used to generate cookies. + The default hash algorithm is QCryptographicHash::Sha256 if Qt was configured + to support it, QCryptographicHash::Sha1 otherwise. The default secret is + obtained from the backend-specific cryptographically strong pseudorandom + number generator. + + \sa QCryptographicHash::Algorithm, QDtlsClientVerifier::GeneratorParameters, + setCookieGeneratorParameters() +*/ +QDtlsClientVerifier::GeneratorParameters QDtlsClientVerifier::cookieGeneratorParameters() const +{ + Q_D(const QDtlsClientVerifier); + + return {d->hashAlgorithm, d->secret}; +} + +/*! + \a socket must be a valid pointer, \a dgram must be a non-empty + datagram, \a address cannot be null, broadcast, or multicast. + \a port is the remote peer's port. This function returns \c true + if \a dgram contains a ClientHello message with a valid cookie. + If no matching cookie is found, verifyClient() will send a + HelloVerifyRequest message using \a socket and return \c false. + + The following snippet shows how a server application may check for errors: + + \snippet code/src_network_ssl_qdtlscookie.cpp 2 + + \sa QHostAddress::isNull(), QHostAddress::isBroadcast(), QHostAddress::isMulticast(), + setCookieGeneratorParameters(), cookieGeneratorParameters() +*/ +bool QDtlsClientVerifier::verifyClient(QUdpSocket *socket, const QByteArray &dgram, + const QHostAddress &address, quint16 port) +{ + Q_D(QDtlsClientVerifier); + + if (!socket || address.isNull() || !dgram.size()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + tr("A valid UDP socket, non-empty datagram, valid address/port were expected")); + return false; + } + + if (address.isBroadcast() || address.isMulticast()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + msgUnsupportedMulticastAddress()); + return false; + } + + return d->verifyClient(socket, dgram, address, port); +} + +/*! + Convenience function. Returns the last ClientHello message that was successfully + verified, or an empty QByteArray if no verification has completed. + + \sa verifyClient() +*/ +QByteArray QDtlsClientVerifier::verifiedHello() const +{ + Q_D(const QDtlsClientVerifier); + + return d->verifiedClientHello; +} + +/*! + Returns the last error that occurred or QDtlsError::NoError. + + \sa QDtlsError, dtlsErrorString() +*/ +QDtlsError QDtlsClientVerifier::dtlsError() const +{ + Q_D(const QDtlsClientVerifier); + + return d->errorCode; +} + +/*! + Returns a textual description of the last error, or an empty string. + + \sa dtlsError() + */ +QString QDtlsClientVerifier::dtlsErrorString() const +{ + Q_D(const QDtlsBase); + + return d->errorDescription; +} + +/*! + Creates a QDtls object, \a parent is passed to the QObject constructor. + \a mode is QSslSocket::SslServerMode for a server-side DTLS connection or + QSslSocket::SslClientMode for a client. + + \sa sslMode(), QSslSocket::SslMode +*/ +QDtls::QDtls(QSslSocket::SslMode mode, QObject *parent) + : QObject(*new QDtlsPrivateOpenSSL, parent) +{ + Q_D(QDtls); + + d->mode = mode; + setDtlsConfiguration(QSslConfiguration::defaultDtlsConfiguration()); +} + +/*! + Destroys the QDtls object. +*/ +QDtls::~QDtls() +{ +} + +/*! + Sets the peer's address, \a port, and host name and returns \c true + if successful. \a address must not be null, multicast, or broadcast. + \a verificationName is the host name used for the certificate validation. + + \sa peerAddress(), peerPort(), peerVerificationName() + */ +bool QDtls::setPeer(const QHostAddress &address, quint16 port, + const QString &verificationName) +{ + Q_D(QDtls); + + if (d->handshakeState != HandshakeNotStarted) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot set peer after handshake started")); + return false; + } + + if (address.isNull()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + tr("Invalid address")); + return false; + } + + if (address.isBroadcast() || address.isMulticast()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + msgUnsupportedMulticastAddress()); + return false; + } + + d->clearDtlsError(); + + d->remoteAddress = address; + d->remotePort = port; + d->peerVerificationName = verificationName; + + return true; +} + +/*! + Sets the host \a name that will be used for the certificate validation + and returns \c true if successful. + + \note This function must be called before the handshake starts. + + \sa peerVerificationName(), setPeer() +*/ +bool QDtls::setPeerVerificationName(const QString &name) +{ + Q_D(QDtls); + + if (d->handshakeState != HandshakeNotStarted) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot set verification name after handshake started")); + return false; + } + + d->clearDtlsError(); + d->peerVerificationName = name; + + return true; +} + +/*! + Returns the peer's address, set by setPeer(), or QHostAddress::Null. + + \sa setPeer() +*/ +QHostAddress QDtls::peerAddress() const +{ + Q_D(const QDtls); + + return d->remoteAddress; +} + +/*! + Returns the peer's port number, set by setPeer(), or 0. + + \sa setPeer() +*/ +quint16 QDtls::peerPort() const +{ + Q_D(const QDtlsBase); + + return d->remotePort; +} + +/*! + Returns the host name set by setPeer() or setPeerVerificationName(). + The default value is an empty string. + + \sa setPeerVerificationName(), setPeer() +*/ +QString QDtls::peerVerificationName() const +{ + Q_D(const QDtls); + + return d->peerVerificationName; +} + +/*! + Returns QSslSocket::SslServerMode for a server-side connection and + QSslSocket::SslClientMode for a client. + + \sa QDtls(), QSslSocket::SslMode +*/ +QSslSocket::SslMode QDtls::sslMode() const +{ + Q_D(const QDtls); + + return d->mode; +} + +/*! + \a mtuHint is the maximum transmission unit (MTU), either discovered or guessed + by the application. The application is not required to set this value. + + \sa mtuHint(), QAbstractSocket::PathMtuSocketOption + */ +void QDtls::setMtuHint(quint16 mtuHint) +{ + Q_D(QDtls); + + d->mtuHint = mtuHint; +} + +/*! + Returns the value previously set by setMtuHint(). The default value is 0. + + \sa setMtuHint() + */ +quint16 QDtls::mtuHint() const +{ + Q_D(const QDtls); + + return d->mtuHint; +} + +/*! + Sets the cryptographic hash algorithm and the secret from \a params. + This function is only needed for a server-side QDtls connection. + Returns \c true if successful. + + \note This function must be called before the handshake starts. + + \sa cookieGeneratorParameters(), doHandshake(), QDtlsClientVerifier, + QDtlsClientVerifier::cookieGeneratorParameters() +*/ +bool QDtls::setCookieGeneratorParameters(const GeneratorParameters ¶ms) +{ + Q_D(QDtls); + + return d->setCookieGeneratorParameters(params.hash, params.secret); +} + +/*! + Returns the current hash algorithm and secret, either default ones or previously + set by a call to setCookieGeneratorParameters(). + + The default hash algorithm is QCryptographicHash::Sha256 if Qt was + configured to support it, QCryptographicHash::Sha1 otherwise. The default + secret is obtained from the backend-specific cryptographically strong + pseudorandom number generator. + + \sa QDtlsClientVerifier, cookieGeneratorParameters() +*/ +QDtls::GeneratorParameters QDtls::cookieGeneratorParameters() const +{ + Q_D(const QDtls); + + return {d->hashAlgorithm, d->secret}; +} + +/*! + Sets the connection's TLS configuration from \a configuration + and returns \c true if successful. + + \note This function must be called before the handshake starts. + + \sa dtlsConfiguration(), doHandshake() +*/ +bool QDtls::setDtlsConfiguration(const QSslConfiguration &configuration) +{ + Q_D(QDtls); + + if (d->handshakeState != HandshakeNotStarted) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot set configuration after handshake started")); + return false; + } + + d->setConfiguration(configuration); + return true; +} + +/*! + Returns either the default DTLS configuration or the configuration set by an + earlier call to setDtlsConfiguration(). + + \sa setDtlsConfiguration(), QSslConfiguration::defaultDtlsConfiguration() +*/ +QSslConfiguration QDtls::dtlsConfiguration() const +{ + Q_D(const QDtls); + + return d->configuration(); +} + +/*! + Returns the current handshake state for this QDtls. + + \sa doHandshake(), QDtls::HandshakeState + */ +QDtls::HandshakeState QDtls::handshakeState()const +{ + Q_D(const QDtls); + + return d->handshakeState; +} + +/*! + Starts or continues a DTLS handshake. \a socket must be a valid pointer. + When starting a server-side DTLS handshake, \a dgram must contain the initial + ClientHello message read from QUdpSocket. This function returns \c true if + no error was found. Handshake state can be tested using handshakeState(). + \c false return means some error occurred, use dtlsError() for more + detailed information. + + \note If the identity of the peer can't be established, the error is set to + QDtlsError::PeerVerificationError. If you want to ignore verification errors + and continue connecting, you must call ignoreVerificationErrors() and then + resumeHandshake(). If the errors cannot be ignored, you must call + abortHandshake(). + + \snippet code/src_network_ssl_qdtls.cpp 5 + + \sa handshakeState(), dtlsError(), ignoreVerificationErrors(), resumeHandshake(), + abortHandshake() +*/ +bool QDtls::doHandshake(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_D(QDtls); + + if (d->handshakeState == HandshakeNotStarted) + return startHandshake(socket, dgram); + else if (d->handshakeState == HandshakeInProgress) + return continueHandshake(socket, dgram); + + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot start/continue handshake, invalid handshake state")); + return false; +} + +/*! + \internal +*/ +bool QDtls::startHandshake(QUdpSocket *socket, const QByteArray &datagram) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return false; + } + + if (d->remoteAddress.isNull()) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("To start a handshake you must set peer's address and port first")); + return false; + } + + if (sslMode() == QSslSocket::SslServerMode && !datagram.size()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + tr("To start a handshake, DTLS server requires non-empty datagram (client hello)")); + return false; + } + + if (d->handshakeState != HandshakeNotStarted) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot start handshake, already done/in progress")); + return false; + } + + return d->startHandshake(socket, datagram); +} + +/*! + If a timeout occures during the handshake, the handshakeTimeout() signal + is emitted. The application must call handleTimeout() to retransmit handshake + messages; handleTimeout() returns \c true if a timeout has occurred, false + otherwise. \a socket must be a valid pointer. + + \sa handshakeTimeout() +*/ +bool QDtls::handleTimeout(QUdpSocket *socket) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return false; + } + + return d->handleTimeout(socket); +} + +/*! + \internal +*/ +bool QDtls::continueHandshake(QUdpSocket *socket, const QByteArray &datagram) +{ + Q_D(QDtls); + + if (!socket || !datagram.size()) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + tr("A valid QUdpSocket and non-empty datagram are needed to continue the handshake")); + return false; + } + + if (d->handshakeState != HandshakeInProgress) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot continue handshake, not in InProgress state")); + return false; + } + + return d->continueHandshake(socket, datagram); +} + +/*! + If peer verification errors were ignored during the handshake, + resumeHandshake() resumes and completes the handshake and returns + \c true. \a socket must be a valid pointer. Returns \c false if + the handshake could not be resumed. + + \sa doHandshake(), abortHandshake() peerVerificationErrors(), ignoreVerificationErrors() +*/ +bool QDtls::resumeHandshake(QUdpSocket *socket) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return false; + } + + if (d->handshakeState != PeerVerificationFailed) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot resume, not in VerificationError state")); + return false; + } + + return d->resumeHandshake(socket); +} + +/*! + Aborts the ongoing handshake. Returns true if one was on-going on \a socket; + otherwise, sets a suitable error and returns false. + + \sa doHandshake(), resumeHandshake() + */ +bool QDtls::abortHandshake(QUdpSocket *socket) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return false; + } + + if (d->handshakeState != PeerVerificationFailed && d->handshakeState != HandshakeInProgress) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("No handshake in progress, nothing to abort")); + return false; + } + + d->abortHandshake(socket); + return true; +} + +/*! + Sends an encrypted shutdown alert message and closes the DTLS connection. + Handshake state changes to QDtls::HandshakeNotStarted. \a socket must be a + valid pointer. This function returns \c true on success. + + \sa doHandshake() + */ +bool QDtls::shutdown(QUdpSocket *socket) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, + tr("Invalid (nullptr) socket")); + return false; + } + + if (!d->connectionEncrypted) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot send shutdown alert, not encrypted")); + return false; + } + + d->sendShutdownAlert(socket); + return true; +} + +/*! + Returns \c true if DTLS handshake completed successfully. + + \sa doHandshake(), handshakeState() + */ +bool QDtls::isConnectionEncrypted() const +{ + Q_D(const QDtls); + + return d->connectionEncrypted; +} + +/*! + Returns the cryptographic \l {QSslCipher} {cipher} used by this connection, + or a null cipher if the connection isn't encrypted. The cipher for the + session is selected during the handshake phase. The cipher is used to encrypt + and decrypt data. + + QSslConfiguration provides functions for setting the ordered list of ciphers + from which the handshake phase will eventually select the session cipher. + This ordered list must be in place before the handshake phase begins. + + \sa QSslConfiguration, setDtlsConfiguration(), dtlsConfiguration() +*/ +QSslCipher QDtls::sessionCipher() const +{ + Q_D(const QDtls); + + return d->sessionCipher; +} + +/*! + Returns the DTLS protocol version used by this connection, or UnknownProtocol + if the connection isn't encrypted yet. The protocol for the connection is selected + during the handshake phase. + + setDtlsConfiguration() can set the preferred version before the handshake starts. + + \sa setDtlsConfiguration(), QSslConfiguration, QSslConfiguration::defaultDtlsConfiguration(), + QSslConfiguration::setProtocol() +*/ +QSsl::SslProtocol QDtls::sessionProtocol() const +{ + Q_D(const QDtls); + + return d->sessionProtocol; +} + +/*! + Encrypts \a dgram and writes the encrypted data into \a socket. Returns the + number of bytes written, or -1 in case of error. The handshake must be completed + before writing encrypted data. \a socket must be a valid + pointer. + + \sa doHandshake(), handshakeState(), isConnectionEncrypted(), dtlsError() +*/ +qint64 QDtls::writeDatagramEncrypted(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return -1; + } + + if (!isConnectionEncrypted()) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot write a datagram, not in encrypted state")); + return -1; + } + + return d->writeDatagramEncrypted(socket, dgram); +} + +/*! + Decrypts \a dgram and returns its contents as plain text. The handshake must + be completed before datagrams can be decrypted. Depending on the type of the + TLS message the connection may write into \a socket, which must be a valid + pointer. +*/ +QByteArray QDtls::decryptDatagram(QUdpSocket *socket, const QByteArray &dgram) +{ + Q_D(QDtls); + + if (!socket) { + d->setDtlsError(QDtlsError::InvalidInputParameters, tr("Invalid (nullptr) socket")); + return {}; + } + + if (!isConnectionEncrypted()) { + d->setDtlsError(QDtlsError::InvalidOperation, + tr("Cannot read a datagram, not in encrypted state")); + return {}; + } + + if (!dgram.size()) + return {}; + + return d->decryptDatagram(socket, dgram); +} + +/*! + Returns the last error encountered by the connection or QDtlsError::NoError. + + \sa dtlsErrorString(), QDtlsError +*/ +QDtlsError QDtls::dtlsError() const +{ + Q_D(const QDtls); + + return d->errorCode; +} + +/*! + Returns a textual description for the last error encountered by the connection + or empty string. + + \sa dtlsError() +*/ +QString QDtls::dtlsErrorString() const +{ + Q_D(const QDtls); + + return d->errorDescription; +} + +/*! + Returns errors found while establishing the identity of the peer. + + If you want to continue connecting despite the errors that have occurred, + you must call ignoreVerificationErrors(). +*/ +QVector<QSslError> QDtls::peerVerificationErrors() const +{ + Q_D(const QDtls); + + return d->tlsErrors; +} + +/*! + This method tells QDtls to ignore only the errors given in \a errorsToIgnore. + + If, for instance, you want to connect to a server that uses a self-signed + certificate, consider the following snippet: + + \snippet code/src_network_ssl_qdtls.cpp 6 + + You can also call this function after doHandshake() encountered the + QDtlsError::PeerVerificationError error, and then resume the handshake by + calling resumeHandshake(). + + Later calls to this function will replace the list of errors that were + passed in previous calls. You can clear the list of errors you want to ignore + by calling this function with an empty list. + + \sa doHandshake(), resumeHandshake(), QSslError +*/ +void QDtls::ignoreVerificationErrors(const QVector<QSslError> &errorsToIgnore) +{ + Q_D(QDtls); + + d->tlsErrorsToIgnore = errorsToIgnore; +} + +QT_END_NAMESPACE |