blob: 8566d78aef19bc4e89b7eabf8e879b41c1220795 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2014 BlackBerry Limited. All rights reserved.
** Copyright (C) 2014 Governikus GmbH & Co. KG.
** 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 <QtNetwork/qsslsocket.h>
#include "private/qssl_p.h"
#include "private/qsslcontext_openssl_p.h"
#include "private/qsslsocket_openssl_p.h"
#include "private/qsslsocket_openssl_symbols_p.h"
QT_BEGIN_NAMESPACE
static inline QString msgErrorSettingBackendConfig(const QString &why)
{
return QSslSocket::tr("Error when setting the OpenSSL configuration (%1)").arg(why);
}
QSslContext::QSslContext()
: ctx(nullptr),
pkey(nullptr),
session(nullptr),
m_sessionTicketLifeTimeHint(-1)
{
}
QSslContext::~QSslContext()
{
if (ctx)
// This will decrement the reference count by 1 and free the context eventually when possible
q_SSL_CTX_free(ctx);
if (pkey)
q_EVP_PKEY_free(pkey);
if (session)
q_SSL_SESSION_free(session);
}
QSslContext* QSslContext::fromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading)
{
QSslContext *sslContext = new QSslContext();
initSslContext(sslContext, mode, configuration, allowRootCertOnDemandLoading);
return sslContext;
}
QSharedPointer<QSslContext> QSslContext::sharedFromConfiguration(QSslSocket::SslMode mode, const QSslConfiguration &configuration, bool allowRootCertOnDemandLoading)
{
QSharedPointer<QSslContext> sslContext = QSharedPointer<QSslContext>::create();
initSslContext(sslContext.data(), mode, configuration, allowRootCertOnDemandLoading);
return sslContext;
}
#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_NEXTPROTONEG)
static int next_proto_cb(SSL *, unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen, void *arg)
{
QSslContext::NPNContext *ctx = reinterpret_cast<QSslContext::NPNContext *>(arg);
// comment out to debug:
// QList<QByteArray> supportedVersions;
// for (unsigned int i = 0; i < inlen; ) {
// QByteArray version(reinterpret_cast<const char *>(&in[i+1]), in[i]);
// supportedVersions << version;
// i += in[i] + 1;
// }
int proto = q_SSL_select_next_proto(out, outlen, in, inlen, ctx->data, ctx->len);
switch (proto) {
case OPENSSL_NPN_UNSUPPORTED:
ctx->status = QSslConfiguration::NextProtocolNegotiationNone;
break;
case OPENSSL_NPN_NEGOTIATED:
ctx->status = QSslConfiguration::NextProtocolNegotiationNegotiated;
break;
case OPENSSL_NPN_NO_OVERLAP:
ctx->status = QSslConfiguration::NextProtocolNegotiationUnsupported;
break;
default:
qCWarning(lcSsl, "OpenSSL sent unknown NPN status");
}
return SSL_TLSEXT_ERR_OK;
}
QSslContext::NPNContext QSslContext::npnContext() const
{
return m_npnContext;
}
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
// Needs to be deleted by caller
SSL* QSslContext::createSsl()
{
SSL* ssl = q_SSL_new(ctx);
q_SSL_clear(ssl);
if (!session && !sessionASN1().isEmpty()
&& !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
const unsigned char *data = reinterpret_cast<const unsigned char *>(m_sessionASN1.constData());
session = q_d2i_SSL_SESSION(
nullptr, &data, m_sessionASN1.size()); // refcount is 1 already, set by function above
}
if (session) {
// Try to resume the last session we cached
if (!q_SSL_set_session(ssl, session)) {
qCWarning(lcSsl, "could not set SSL session");
q_SSL_SESSION_free(session);
session = nullptr;
}
}
#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_NEXTPROTONEG)
QList<QByteArray> protocols = sslConfiguration.d->nextAllowedProtocols;
if (!protocols.isEmpty()) {
m_supportedNPNVersions.clear();
for (int a = 0; a < protocols.count(); ++a) {
if (protocols.at(a).size() > 255) {
qCWarning(lcSsl) << "TLS NPN extension" << protocols.at(a)
<< "is too long and will be ignored.";
continue;
} else if (protocols.at(a).isEmpty()) {
continue;
}
m_supportedNPNVersions.append(protocols.at(a).size()).append(protocols.at(a));
}
if (m_supportedNPNVersions.size()) {
m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data());
m_npnContext.len = m_supportedNPNVersions.count();
m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone;
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (QSslSocket::sslLibraryVersionNumber() >= 0x10002000L) {
// Callback's type has a parameter 'const unsigned char ** out'
// since it was introduced in 1.0.2. Internally, OpenSSL's own code
// (tests/examples) cast it to unsigned char * (since it's 'out').
// We just re-use our NPN callback and cast here:
typedef int (*alpn_callback_t) (SSL *, const unsigned char **, unsigned char *,
const unsigned char *, unsigned int, void *);
// With ALPN callback is for a server side only, for a client m_npnContext.status
// will stay in NextProtocolNegotiationNone.
q_SSL_CTX_set_alpn_select_cb(ctx, alpn_callback_t(next_proto_cb), &m_npnContext);
// Client:
q_SSL_set_alpn_protos(ssl, m_npnContext.data, m_npnContext.len);
}
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ...
// And in case our peer does not support ALPN, but supports NPN:
q_SSL_CTX_set_next_proto_select_cb(ctx, next_proto_cb, &m_npnContext);
}
}
#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ...
return ssl;
}
// We cache exactly one session here
bool QSslContext::cacheSession(SSL* ssl)
{
// don't cache the same session again
if (session && session == q_SSL_get_session(ssl))
return true;
// decrease refcount of currently stored session
// (this might happen if there are several concurrent handshakes in flight)
if (session)
q_SSL_SESSION_free(session);
// cache the session the caller gave us and increase reference count
session = q_SSL_get1_session(ssl);
if (session && !sslConfiguration.testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
int sessionSize = q_i2d_SSL_SESSION(session, nullptr);
if (sessionSize > 0) {
m_sessionASN1.resize(sessionSize);
unsigned char *data = reinterpret_cast<unsigned char *>(m_sessionASN1.data());
if (!q_i2d_SSL_SESSION(session, &data))
qCWarning(lcSsl, "could not store persistent version of SSL session");
m_sessionTicketLifeTimeHint = q_SSL_SESSION_get_ticket_lifetime_hint(session);
}
}
return (session != nullptr);
}
QByteArray QSslContext::sessionASN1() const
{
return m_sessionASN1;
}
void QSslContext::setSessionASN1(const QByteArray &session)
{
m_sessionASN1 = session;
}
int QSslContext::sessionTicketLifeTimeHint() const
{
return m_sessionTicketLifeTimeHint;
}
QSslError::SslError QSslContext::error() const
{
return errorCode;
}
QString QSslContext::errorString() const
{
return errorStr;
}
#if QT_CONFIG(ocsp)
extern "C" int qt_OCSP_status_server_callback(SSL *ssl, void *); // Defined in qsslsocket_openssl.cpp.
#endif // ocsp
// static
void QSslContext::applyBackendConfig(QSslContext *sslContext)
{
const QMap<QByteArray, QVariant> &conf = sslContext->sslConfiguration.backendConfiguration();
if (conf.isEmpty())
return;
#if QT_CONFIG(ocsp)
auto ocspResponsePos = conf.find("Qt-OCSP-response");
if (ocspResponsePos != conf.end()) {
// This is our private, undocumented configuration option, existing only for
// the purpose of testing OCSP status responses. We don't even check this
// callback was set. If no - the test must fail.
q_SSL_CTX_set_tlsext_status_cb(sslContext->ctx, qt_OCSP_status_server_callback);
if (conf.size() == 1)
return;
}
#endif // ocsp
#if OPENSSL_VERSION_NUMBER >= 0x10002000L
if (QSslSocket::sslLibraryVersionNumber() >= 0x10002000L) {
QSharedPointer<SSL_CONF_CTX> cctx(q_SSL_CONF_CTX_new(), &q_SSL_CONF_CTX_free);
if (cctx) {
q_SSL_CONF_CTX_set_ssl_ctx(cctx.data(), sslContext->ctx);
q_SSL_CONF_CTX_set_flags(cctx.data(), SSL_CONF_FLAG_FILE);
for (auto i = conf.constBegin(); i != conf.constEnd(); ++i) {
if (i.key() == "Qt-OCSP-response") // This never goes to SSL_CONF_cmd().
continue;
if (!i.value().canConvert(QMetaType::QByteArray)) {
sslContext->errorCode = QSslError::UnspecifiedError;
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Expecting QByteArray for %1").arg(
QString::fromUtf8(i.key())));
return;
}
const QByteArray &value = i.value().toByteArray();
const int result = q_SSL_CONF_cmd(cctx.data(), i.key().constData(), value.constData());
if (result == 2)
continue;
sslContext->errorCode = QSslError::UnspecifiedError;
switch (result) {
case 0:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("An error occurred attempting to set %1 to %2").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
case 1:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Wrong value for %1 (%2)").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
default:
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("Unrecognized command %1 = %2").arg(
QString::fromUtf8(i.key()), QString::fromUtf8(value)));
return;
}
}
if (q_SSL_CONF_CTX_finish(cctx.data()) == 0) {
sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_finish() failed"));
sslContext->errorCode = QSslError::UnspecifiedError;
}
} else {
sslContext->errorStr = msgErrorSettingBackendConfig(QSslSocket::tr("SSL_CONF_CTX_new() failed"));
sslContext->errorCode = QSslError::UnspecifiedError;
}
} else
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
{
// specific algorithms requested, but not possible to set
sslContext->errorCode = QSslError::UnspecifiedError;
sslContext->errorStr = msgErrorSettingBackendConfig(
QSslSocket::tr("OpenSSL version too old, need at least v1.0.2"));
}
}
QT_END_NAMESPACE