2011-04-27 10:05:43 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
** Copyright (C) 2017 The Qt Company Ltd.
|
2014-09-03 09:12:12 +00:00
|
|
|
** Copyright (C) 2014 Governikus GmbH & Co. KG
|
2016-01-15 07:08:27 +00:00
|
|
|
** Contact: https://www.qt.io/licensing/
|
2011-04-27 10:05:43 +00:00
|
|
|
**
|
|
|
|
** This file is part of the QtNetwork module of the Qt Toolkit.
|
|
|
|
**
|
2016-01-15 07:08:27 +00:00
|
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
2012-09-19 12:28:29 +00:00
|
|
|
** 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
|
2015-01-28 08:44:43 +00:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
2016-01-15 07:08:27 +00:00
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2012-09-19 12:28:29 +00:00
|
|
|
**
|
2011-04-27 10:05:43 +00:00
|
|
|
** GNU Lesser General Public License Usage
|
2012-09-19 12:28:29 +00:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
2016-01-15 07:08:27 +00:00
|
|
|
** 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.
|
2011-04-27 10:05:43 +00:00
|
|
|
**
|
|
|
|
** $QT_END_LICENSE$
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
2013-02-05 18:22:11 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** In addition, as a special exception, the copyright holders listed above give
|
|
|
|
** permission to link the code of its release of Qt with the OpenSSL project's
|
|
|
|
** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the
|
|
|
|
** same license as the original version), and distribute the linked executables.
|
|
|
|
**
|
|
|
|
** You must comply with the GNU General Public License version 2 in all
|
|
|
|
** respects for all of the code used other than the "OpenSSL" code. If you
|
|
|
|
** modify this file, you may extend this exception to your version of the file,
|
|
|
|
** but you are not obligated to do so. If you do not wish to do so, delete
|
|
|
|
** this exception statement from your version of this file.
|
|
|
|
**
|
|
|
|
****************************************************************************/
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
//#define QSSLSOCKET_DEBUG
|
|
|
|
|
2014-12-08 12:35:47 +00:00
|
|
|
#include "qssl_p.h"
|
2011-04-27 10:05:43 +00:00
|
|
|
#include "qsslsocket_openssl_p.h"
|
|
|
|
#include "qsslsocket_openssl_symbols_p.h"
|
|
|
|
#include "qsslsocket.h"
|
|
|
|
#include "qsslcertificate_p.h"
|
|
|
|
#include "qsslcipher_p.h"
|
2014-05-10 21:49:37 +00:00
|
|
|
#include "qsslkey_p.h"
|
2014-09-03 09:12:12 +00:00
|
|
|
#include "qsslellipticcurve.h"
|
2014-10-15 13:13:47 +00:00
|
|
|
#include "qsslpresharedkeyauthenticator.h"
|
|
|
|
#include "qsslpresharedkeyauthenticator_p.h"
|
2019-01-16 13:43:09 +00:00
|
|
|
#include "qocspresponse_p.h"
|
2019-09-27 11:04:54 +00:00
|
|
|
#include "qsslkey.h"
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2018-03-22 15:45:04 +00:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#include "qwindowscarootfetcher_p.h"
|
|
|
|
#endif
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
#include <QtCore/qdatetime.h>
|
|
|
|
#include <QtCore/qdebug.h>
|
|
|
|
#include <QtCore/qdir.h>
|
|
|
|
#include <QtCore/qdiriterator.h>
|
|
|
|
#include <QtCore/qelapsedtimer.h>
|
|
|
|
#include <QtCore/qfile.h>
|
|
|
|
#include <QtCore/qfileinfo.h>
|
|
|
|
#include <QtCore/qmutex.h>
|
|
|
|
#include <QtCore/qthread.h>
|
|
|
|
#include <QtCore/qurl.h>
|
|
|
|
#include <QtCore/qvarlengtharray.h>
|
2018-05-08 10:35:46 +00:00
|
|
|
#include <QtCore/qscopedvaluerollback.h>
|
2019-09-27 11:04:54 +00:00
|
|
|
#include <QtCore/qlibrary.h>
|
|
|
|
#include <QtCore/qoperatingsystemversion.h>
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
#if QT_CONFIG(ocsp)
|
2019-01-29 09:34:26 +00:00
|
|
|
#include "qocsp_p.h"
|
2018-10-25 08:44:16 +00:00
|
|
|
#endif
|
|
|
|
|
2018-11-13 14:25:25 +00:00
|
|
|
#include <algorithm>
|
2019-11-26 12:35:59 +00:00
|
|
|
#include <memory>
|
2018-11-13 14:25:25 +00:00
|
|
|
|
2014-10-15 13:13:47 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
namespace {
|
|
|
|
|
2020-06-25 08:44:57 +00:00
|
|
|
QSsl::AlertLevel tlsAlertLevel(int value)
|
2019-10-30 10:49:07 +00:00
|
|
|
{
|
2020-06-25 08:44:57 +00:00
|
|
|
using QSsl::AlertLevel;
|
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
if (const char *typeString = q_SSL_alert_type_string(value)) {
|
|
|
|
// Documented to return 'W' for warning, 'F' for fatal,
|
|
|
|
// 'U' for unknown.
|
|
|
|
switch (typeString[0]) {
|
|
|
|
case 'W':
|
2020-06-25 08:44:57 +00:00
|
|
|
return AlertLevel::Warning;
|
2019-10-30 10:49:07 +00:00
|
|
|
case 'F':
|
2020-06-25 08:44:57 +00:00
|
|
|
return AlertLevel::Fatal;
|
2019-10-30 10:49:07 +00:00
|
|
|
default:;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 08:44:57 +00:00
|
|
|
return AlertLevel::Unknown;
|
2019-10-30 10:49:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString tlsAlertDescription(int value)
|
|
|
|
{
|
|
|
|
QString description = QLatin1String(q_SSL_alert_desc_string_long(value));
|
|
|
|
if (!description.size())
|
|
|
|
description = QLatin1String("no description provided");
|
|
|
|
return description;
|
|
|
|
}
|
|
|
|
|
2020-06-25 08:44:57 +00:00
|
|
|
QSsl::AlertType tlsAlertType(int value)
|
2019-10-30 10:49:07 +00:00
|
|
|
{
|
|
|
|
// In case for some reason openssl gives us a value,
|
|
|
|
// which is not in our enum actually, we leave it to
|
|
|
|
// an application to handle (supposedly they have
|
|
|
|
// if or switch-statements).
|
2020-06-25 08:44:57 +00:00
|
|
|
return QSsl::AlertType(value & 0xff);
|
2019-10-30 10:49:07 +00:00
|
|
|
}
|
|
|
|
|
2020-05-14 14:40:08 +00:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
2020-06-22 10:25:41 +00:00
|
|
|
QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool checkAIA)
|
2020-05-14 14:40:08 +00:00
|
|
|
{
|
|
|
|
QSslCertificate certToFetch;
|
|
|
|
|
|
|
|
for (const auto &tlsError : tlsErrors) {
|
|
|
|
switch (tlsError.error()) {
|
|
|
|
case QSslError::UnableToGetLocalIssuerCertificate: // site presented intermediate cert, but root is unknown
|
|
|
|
case QSslError::SelfSignedCertificateInChain: // site presented a complete chain, but root is unknown
|
|
|
|
certToFetch = tlsError.certificate();
|
|
|
|
break;
|
|
|
|
case QSslError::SelfSignedCertificate:
|
|
|
|
case QSslError::CertificateBlacklisted:
|
|
|
|
//With these errors, we know it will be untrusted so save time by not asking windows
|
|
|
|
return QSslCertificate{};
|
|
|
|
default:
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
|
|
|
qCDebug(lcSsl) << tlsError.errorString();
|
|
|
|
#endif
|
|
|
|
//TODO - this part is strange.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (checkAIA) {
|
|
|
|
const auto extensions = certToFetch.extensions();
|
|
|
|
for (const auto &ext : extensions) {
|
|
|
|
if (ext.oid() == QStringLiteral("1.3.6.1.5.5.7.1.1")) // See RFC 4325
|
|
|
|
return certToFetch;
|
|
|
|
}
|
|
|
|
//The only reason we check this extensions is because an application set trusted
|
|
|
|
//CA certificates explicitly, thus technically disabling CA fetch. So, if it's
|
|
|
|
//the case and an intermediate certificate is missing, and no extensions is
|
|
|
|
//present on the leaf certificate - we fail the handshake immediately.
|
|
|
|
return QSslCertificate{};
|
|
|
|
}
|
|
|
|
|
|
|
|
return certToFetch;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // Q_OS_WIN
|
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
} // Unnamed namespace
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
|
|
|
|
void qt_AlertInfoCallback(const SSL *connection, int from, int value)
|
|
|
|
{
|
|
|
|
// Passed to SSL_set_info_callback()
|
|
|
|
// https://www.openssl.org/docs/man1.1.1/man3/SSL_set_info_callback.html
|
|
|
|
|
|
|
|
if (!connection) {
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
|
|
|
qCWarning(lcSsl, "Invalid 'connection' parameter (nullptr)");
|
|
|
|
#endif // QSSLSOCKET_DEBUG
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
|
|
|
|
+ QSslSocketBackendPrivate::socketOffsetInExData;
|
|
|
|
auto privateSocket =
|
|
|
|
static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(connection, offset));
|
|
|
|
if (!privateSocket) {
|
|
|
|
// SSL_set_ex_data can fail:
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
|
|
|
qCWarning(lcSsl, "No external data (socket backend) found for parameter 'connection'");
|
|
|
|
#endif // QSSLSOCKET_DEBUG
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(from & SSL_CB_ALERT)) {
|
|
|
|
// We only want to know about alerts (at least for now).
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (from & SSL_CB_WRITE)
|
|
|
|
privateSocket->alertMessageSent(value);
|
|
|
|
else
|
|
|
|
privateSocket->alertMessageReceived(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
int q_X509CallbackDirect(int ok, X509_STORE_CTX *ctx)
|
|
|
|
{
|
|
|
|
// Passed to SSL_CTX_set_verify()
|
|
|
|
// https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_verify.html
|
|
|
|
// Returns 0 to abort verification, 1 to continue.
|
|
|
|
|
|
|
|
// This is a new, experimental verification callback, reporting
|
|
|
|
// errors immediately and returning 0 or 1 depending on an application
|
|
|
|
// either ignoring or not ignoring verification errors as they come.
|
|
|
|
if (!ctx) {
|
|
|
|
qCWarning(lcSsl, "Invalid store context (nullptr)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok) {
|
|
|
|
// "Whenever a X509_STORE_CTX object is created for the verification of the
|
|
|
|
// peer's certificate during a handshake, a pointer to the SSL object is
|
|
|
|
// stored into the X509_STORE_CTX object to identify the connection affected.
|
|
|
|
// To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be
|
|
|
|
// used with the correct index."
|
|
|
|
SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx()));
|
|
|
|
if (!ssl) {
|
|
|
|
qCWarning(lcSsl, "No external data (SSL) found in X509 store object");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
|
|
|
|
+ QSslSocketBackendPrivate::socketOffsetInExData;
|
|
|
|
auto privateSocket = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, offset));
|
|
|
|
if (!privateSocket) {
|
|
|
|
qCWarning(lcSsl, "No external data (QSslSocketBackendPrivate) found in SSL object");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return privateSocket->emitErrorFromCallback(ctx);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // extern "C"
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex)
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
bool QSslSocketPrivate::s_libraryLoaded = false;
|
|
|
|
bool QSslSocketPrivate::s_loadedCiphersAndCerts = false;
|
|
|
|
bool QSslSocketPrivate::s_loadRootCertsOnDemand = false;
|
2014-10-15 13:13:47 +00:00
|
|
|
int QSslSocketBackendPrivate::s_indexForSSLExtraData = -1;
|
|
|
|
|
2012-12-21 13:02:38 +00:00
|
|
|
QString QSslSocketBackendPrivate::getErrorsFromOpenSsl()
|
|
|
|
{
|
|
|
|
QString errorString;
|
2018-02-19 12:21:46 +00:00
|
|
|
char buf[256] = {}; // OpenSSL docs claim both 120 and 256; use the larger.
|
2012-12-21 13:02:38 +00:00
|
|
|
unsigned long errNum;
|
|
|
|
while ((errNum = q_ERR_get_error())) {
|
2018-02-19 12:21:46 +00:00
|
|
|
if (!errorString.isEmpty())
|
2012-12-21 13:02:38 +00:00
|
|
|
errorString.append(QLatin1String(", "));
|
2018-02-19 12:21:46 +00:00
|
|
|
q_ERR_error_string_n(errNum, buf, sizeof buf);
|
|
|
|
errorString.append(QString::fromLatin1(buf)); // error is ascii according to man ERR_error_string
|
2012-12-21 13:02:38 +00:00
|
|
|
}
|
|
|
|
return errorString;
|
|
|
|
}
|
|
|
|
|
2020-05-04 10:49:53 +00:00
|
|
|
void QSslSocketBackendPrivate::logAndClearErrorQueue()
|
|
|
|
{
|
|
|
|
const auto errors = getErrorsFromOpenSsl();
|
|
|
|
if (errors.size())
|
|
|
|
qCWarning(lcSsl) << "Discarding errors:" << errors;
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
extern "C" {
|
2014-10-15 13:13:47 +00:00
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
#ifndef OPENSSL_NO_PSK
|
2014-10-15 13:13:47 +00:00
|
|
|
static unsigned int q_ssl_psk_client_callback(SSL *ssl,
|
|
|
|
const char *hint,
|
|
|
|
char *identity, unsigned int max_identity_len,
|
|
|
|
unsigned char *psk, unsigned int max_psk_len)
|
|
|
|
{
|
|
|
|
QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
Q_ASSERT(d);
|
|
|
|
return d->tlsPskClientCallback(hint, identity, max_identity_len, psk, max_psk_len);
|
|
|
|
}
|
2016-03-23 11:27:11 +00:00
|
|
|
|
|
|
|
static unsigned int q_ssl_psk_server_callback(SSL *ssl,
|
|
|
|
const char *identity,
|
|
|
|
unsigned char *psk, unsigned int max_psk_len)
|
|
|
|
{
|
|
|
|
QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
Q_ASSERT(d);
|
|
|
|
return d->tlsPskServerCallback(identity, psk, max_psk_len);
|
|
|
|
}
|
2018-12-13 14:39:26 +00:00
|
|
|
|
|
|
|
#ifdef TLS1_3_VERSION
|
|
|
|
static unsigned int q_ssl_psk_restore_client(SSL *ssl,
|
|
|
|
const char *hint,
|
|
|
|
char *identity, unsigned int max_identity_len,
|
|
|
|
unsigned char *psk, unsigned int max_psk_len)
|
|
|
|
{
|
|
|
|
Q_UNUSED(hint);
|
|
|
|
Q_UNUSED(identity);
|
|
|
|
Q_UNUSED(max_identity_len);
|
|
|
|
Q_UNUSED(psk);
|
|
|
|
Q_UNUSED(max_psk_len);
|
|
|
|
|
|
|
|
#ifdef QT_DEBUG
|
|
|
|
QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
Q_ASSERT(d);
|
|
|
|
Q_ASSERT(d->mode == QSslSocket::SslClientMode);
|
|
|
|
#endif
|
|
|
|
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int q_ssl_psk_use_session_callback(SSL *ssl, const EVP_MD *md, const unsigned char **id,
|
|
|
|
size_t *idlen, SSL_SESSION **sess)
|
|
|
|
{
|
|
|
|
Q_UNUSED(ssl);
|
|
|
|
Q_UNUSED(md);
|
|
|
|
Q_UNUSED(id);
|
|
|
|
Q_UNUSED(idlen);
|
|
|
|
Q_UNUSED(sess);
|
|
|
|
|
|
|
|
#ifdef QT_DEBUG
|
|
|
|
QSslSocketBackendPrivate *d = reinterpret_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
Q_ASSERT(d);
|
|
|
|
Q_ASSERT(d->mode == QSslSocket::SslClientMode);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Temporarily rebind the psk because it will be called next. The function will restore it.
|
|
|
|
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_restore_client);
|
|
|
|
|
|
|
|
return 1; // need to return 1 or else "the connection setup fails."
|
|
|
|
}
|
2020-01-27 13:11:08 +00:00
|
|
|
|
|
|
|
int q_ssl_sess_set_new_cb(SSL *ssl, SSL_SESSION *session)
|
|
|
|
{
|
|
|
|
if (!ssl) {
|
|
|
|
qCWarning(lcSsl, "Invalid SSL (nullptr)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!session) {
|
|
|
|
qCWarning(lcSsl, "Invalid SSL_SESSION (nullptr)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto socketPrivate = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl,
|
|
|
|
QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
return socketPrivate->handleNewSessionTicket(ssl);
|
|
|
|
}
|
2018-12-13 14:39:26 +00:00
|
|
|
#endif // TLS1_3_VERSION
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
#endif // !OPENSSL_NO_PSK
|
2018-11-13 14:25:25 +00:00
|
|
|
|
|
|
|
#if QT_CONFIG(ocsp)
|
|
|
|
|
|
|
|
int qt_OCSP_status_server_callback(SSL *ssl, void *ocspRequest)
|
|
|
|
{
|
2020-06-27 12:18:09 +00:00
|
|
|
Q_UNUSED(ocspRequest);
|
2018-11-13 14:25:25 +00:00
|
|
|
if (!ssl)
|
|
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
|
|
|
|
|
|
auto d = static_cast<QSslSocketBackendPrivate *>(q_SSL_get_ex_data(ssl, QSslSocketBackendPrivate::s_indexForSSLExtraData));
|
|
|
|
if (!d)
|
|
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
|
|
|
|
|
|
Q_ASSERT(d->mode == QSslSocket::SslServerMode);
|
|
|
|
const QByteArray &response = d->ocspResponseDer;
|
|
|
|
Q_ASSERT(response.size());
|
|
|
|
|
|
|
|
unsigned char *derCopy = static_cast<unsigned char *>(q_OPENSSL_malloc(size_t(response.size())));
|
|
|
|
if (!derCopy)
|
|
|
|
return SSL_TLSEXT_ERR_ALERT_FATAL;
|
|
|
|
|
|
|
|
std::copy(response.data(), response.data() + response.size(), derCopy);
|
|
|
|
// We don't check the return value: internally OpenSSL simply assignes the
|
|
|
|
// pointer (it assumes it now owns this memory btw!) and the length.
|
|
|
|
q_SSL_set_tlsext_status_ocsp_resp(ssl, derCopy, response.size());
|
|
|
|
|
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // ocsp
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
} // extern "C"
|
|
|
|
|
|
|
|
QSslSocketBackendPrivate::QSslSocketBackendPrivate()
|
2018-08-07 08:29:46 +00:00
|
|
|
: ssl(nullptr),
|
|
|
|
readBio(nullptr),
|
|
|
|
writeBio(nullptr),
|
|
|
|
session(nullptr)
|
2011-04-27 10:05:43 +00:00
|
|
|
{
|
|
|
|
// Calls SSL_library_init().
|
|
|
|
ensureInitialized();
|
|
|
|
}
|
|
|
|
|
|
|
|
QSslSocketBackendPrivate::~QSslSocketBackendPrivate()
|
|
|
|
{
|
2012-01-30 15:52:27 +00:00
|
|
|
destroySslContext();
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
QSslCipher QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(const SSL_CIPHER *cipher)
|
2011-04-27 10:05:43 +00:00
|
|
|
{
|
|
|
|
QSslCipher ciph;
|
|
|
|
|
|
|
|
char buf [256];
|
|
|
|
QString descriptionOneLine = QString::fromLatin1(q_SSL_CIPHER_description(cipher, buf, sizeof(buf)));
|
|
|
|
|
2020-05-25 11:12:51 +00:00
|
|
|
const auto descriptionList = QStringView{descriptionOneLine}.split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
2011-04-27 10:05:43 +00:00
|
|
|
if (descriptionList.size() > 5) {
|
|
|
|
// ### crude code.
|
|
|
|
ciph.d->isNull = false;
|
2016-03-30 11:23:12 +00:00
|
|
|
ciph.d->name = descriptionList.at(0).toString();
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2016-03-30 11:23:12 +00:00
|
|
|
QString protoString = descriptionList.at(1).toString();
|
2011-04-27 10:05:43 +00:00
|
|
|
ciph.d->protocolString = protoString;
|
|
|
|
ciph.d->protocol = QSsl::UnknownProtocol;
|
2019-11-13 09:37:36 +00:00
|
|
|
if (protoString == QLatin1String("TLSv1"))
|
2011-11-14 11:33:55 +00:00
|
|
|
ciph.d->protocol = QSsl::TlsV1_0;
|
2012-08-16 23:14:04 +00:00
|
|
|
else if (protoString == QLatin1String("TLSv1.1"))
|
|
|
|
ciph.d->protocol = QSsl::TlsV1_1;
|
|
|
|
else if (protoString == QLatin1String("TLSv1.2"))
|
|
|
|
ciph.d->protocol = QSsl::TlsV1_2;
|
2018-10-29 13:26:15 +00:00
|
|
|
else if (protoString == QLatin1String("TLSv1.3"))
|
|
|
|
ciph.d->protocol = QSsl::TlsV1_3;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
if (descriptionList.at(2).startsWith(QLatin1String("Kx=")))
|
2016-03-30 11:23:12 +00:00
|
|
|
ciph.d->keyExchangeMethod = descriptionList.at(2).mid(3).toString();
|
2011-04-27 10:05:43 +00:00
|
|
|
if (descriptionList.at(3).startsWith(QLatin1String("Au=")))
|
2016-03-30 11:23:12 +00:00
|
|
|
ciph.d->authenticationMethod = descriptionList.at(3).mid(3).toString();
|
2011-04-27 10:05:43 +00:00
|
|
|
if (descriptionList.at(4).startsWith(QLatin1String("Enc=")))
|
2016-03-30 11:23:12 +00:00
|
|
|
ciph.d->encryptionMethod = descriptionList.at(4).mid(4).toString();
|
2011-04-27 10:05:43 +00:00
|
|
|
ciph.d->exportable = (descriptionList.size() > 6 && descriptionList.at(6) == QLatin1String("export"));
|
|
|
|
|
2014-05-18 11:19:32 +00:00
|
|
|
ciph.d->bits = q_SSL_CIPHER_get_bits(cipher, &ciph.d->supportedBits);
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
return ciph;
|
|
|
|
}
|
|
|
|
|
2018-02-19 12:46:21 +00:00
|
|
|
QSslErrorEntry QSslErrorEntry::fromStoreContext(X509_STORE_CTX *ctx)
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
{
|
2017-04-19 23:43:33 +00:00
|
|
|
return {
|
2016-01-17 13:16:43 +00:00
|
|
|
q_X509_STORE_CTX_get_error(ctx),
|
|
|
|
q_X509_STORE_CTX_get_error_depth(ctx)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
#if QT_CONFIG(ocsp)
|
|
|
|
|
|
|
|
QSslError qt_OCSP_response_status_to_QSslError(long code)
|
|
|
|
{
|
|
|
|
switch (code) {
|
|
|
|
case OCSP_RESPONSE_STATUS_MALFORMEDREQUEST:
|
|
|
|
return QSslError::OcspMalformedRequest;
|
|
|
|
case OCSP_RESPONSE_STATUS_INTERNALERROR:
|
|
|
|
return QSslError::OcspInternalError;
|
|
|
|
case OCSP_RESPONSE_STATUS_TRYLATER:
|
|
|
|
return QSslError::OcspTryLater;
|
|
|
|
case OCSP_RESPONSE_STATUS_SIGREQUIRED:
|
|
|
|
return QSslError::OcspSigRequred;
|
|
|
|
case OCSP_RESPONSE_STATUS_UNAUTHORIZED:
|
|
|
|
return QSslError::OcspUnauthorized;
|
|
|
|
case OCSP_RESPONSE_STATUS_SUCCESSFUL:
|
|
|
|
default:
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
2019-02-11 15:07:04 +00:00
|
|
|
QOcspRevocationReason qt_OCSP_revocation_reason(int reason)
|
2019-01-16 13:43:09 +00:00
|
|
|
{
|
|
|
|
switch (reason) {
|
|
|
|
case OCSP_REVOKED_STATUS_NOSTATUS:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::None;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_UNSPECIFIED:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::Unspecified;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_KEYCOMPROMISE:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::KeyCompromise;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_CACOMPROMISE:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::CACompromise;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_AFFILIATIONCHANGED:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::AffiliationChanged;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_SUPERSEDED:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::Superseded;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_CESSATIONOFOPERATION:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::CessationOfOperation;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_CERTIFICATEHOLD:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::CertificateHold;
|
2019-01-16 13:43:09 +00:00
|
|
|
case OCSP_REVOKED_STATUS_REMOVEFROMCRL:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::RemoveFromCRL;
|
2019-01-16 13:43:09 +00:00
|
|
|
default:
|
2019-02-11 15:07:04 +00:00
|
|
|
return QOcspRevocationReason::None;
|
2019-01-16 13:43:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
bool qt_OCSP_certificate_match(OCSP_SINGLERESP *singleResponse, X509 *peerCert, X509 *issuer)
|
|
|
|
{
|
|
|
|
// OCSP_basic_verify does verify that the responder is legit, the response is
|
|
|
|
// correctly signed, CertID is correct. But it does not know which certificate
|
|
|
|
// we were presented with by our peer, so it does not check if it's a response
|
|
|
|
// for our peer's certificate.
|
|
|
|
Q_ASSERT(singleResponse && peerCert && issuer);
|
|
|
|
|
|
|
|
const OCSP_CERTID *certId = q_OCSP_SINGLERESP_get0_id(singleResponse); // Does not increment refcount.
|
|
|
|
if (!certId) {
|
|
|
|
qCWarning(lcSsl, "A SingleResponse without CertID");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
ASN1_OBJECT *md = nullptr;
|
|
|
|
ASN1_INTEGER *reportedSerialNumber = nullptr;
|
|
|
|
const int result = q_OCSP_id_get0_info(nullptr, &md, nullptr, &reportedSerialNumber, const_cast<OCSP_CERTID *>(certId));
|
|
|
|
if (result != 1 || !md || !reportedSerialNumber) {
|
|
|
|
qCWarning(lcSsl, "Failed to extract a hash and serial number from CertID structure");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!q_X509_get_serialNumber(peerCert)) {
|
|
|
|
// Is this possible at all? But we have to check this,
|
|
|
|
// ASN1_INTEGER_cmp (called from OCSP_id_cmp) dereferences
|
|
|
|
// without any checks at all.
|
|
|
|
qCWarning(lcSsl, "No serial number in peer's ceritificate");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int nid = q_OBJ_obj2nid(md);
|
|
|
|
if (nid == NID_undef) {
|
|
|
|
qCWarning(lcSsl, "Unknown hash algorithm in CertID");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const EVP_MD *digest = q_EVP_get_digestbynid(nid); // Does not increment refcount.
|
|
|
|
if (!digest) {
|
|
|
|
qCWarning(lcSsl) << "No digest for nid" << nid;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCSP_CERTID *recreatedId = q_OCSP_cert_to_id(digest, peerCert, issuer);
|
|
|
|
if (!recreatedId) {
|
|
|
|
qCWarning(lcSsl, "Failed to re-create CertID");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const QSharedPointer<OCSP_CERTID> guard(recreatedId, q_OCSP_CERTID_free);
|
|
|
|
|
|
|
|
if (q_OCSP_id_cmp(const_cast<OCSP_CERTID *>(certId), recreatedId)) {
|
|
|
|
qDebug(lcSsl, "Certificate ID mismatch");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Bingo!
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // ocsp
|
|
|
|
|
2012-12-21 13:02:38 +00:00
|
|
|
int q_X509Callback(int ok, X509_STORE_CTX *ctx)
|
2011-04-27 10:05:43 +00:00
|
|
|
{
|
|
|
|
if (!ok) {
|
|
|
|
// Store the error and at which depth the error was detected.
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
|
2020-06-22 10:25:41 +00:00
|
|
|
using ErrorListPtr = QList<QSslErrorEntry> *;
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
ErrorListPtr errors = nullptr;
|
|
|
|
|
|
|
|
// Error list is attached to either 'SSL' or 'X509_STORE'.
|
2019-09-27 11:04:54 +00:00
|
|
|
if (X509_STORE *store = q_X509_STORE_CTX_get0_store(ctx)) // We try store first:
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
errors = ErrorListPtr(q_X509_STORE_get_ex_data(store, 0));
|
|
|
|
|
|
|
|
if (!errors) {
|
|
|
|
// Not found on store? Try SSL and its external data then. According to the OpenSSL's
|
|
|
|
// documentation:
|
|
|
|
//
|
2019-10-30 10:49:07 +00:00
|
|
|
// "Whenever a X509_STORE_CTX object is created for the verification of the
|
|
|
|
// peer's certificate during a handshake, a pointer to the SSL object is
|
|
|
|
// stored into the X509_STORE_CTX object to identify the connection affected.
|
|
|
|
// To retrieve this pointer the X509_STORE_CTX_get_ex_data() function can be
|
|
|
|
// used with the correct index."
|
|
|
|
const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
|
|
|
|
+ QSslSocketBackendPrivate::errorOffsetInExData;
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
if (SSL *ssl = static_cast<SSL *>(q_X509_STORE_CTX_get_ex_data(ctx, q_SSL_get_ex_data_X509_STORE_CTX_idx())))
|
2019-10-30 10:49:07 +00:00
|
|
|
errors = ErrorListPtr(q_SSL_get_ex_data(ssl, offset));
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!errors) {
|
|
|
|
qCWarning(lcSsl, "Neither X509_STORE, nor SSL contains error list, handshake failure");
|
|
|
|
return 0;
|
2012-01-06 16:50:23 +00:00
|
|
|
}
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
|
|
|
|
errors->append(QSslErrorEntry::fromStoreContext(ctx));
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
// Always return OK to allow verification to continue. We handle the
|
2011-04-27 10:05:43 +00:00
|
|
|
// errors gracefully after collecting all errors, after verification has
|
|
|
|
// completed.
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-02-19 12:46:21 +00:00
|
|
|
static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers,
|
|
|
|
QList<QSslCipher> &defaultCiphers)
|
|
|
|
{
|
|
|
|
Q_ASSERT(connection);
|
|
|
|
|
|
|
|
STACK_OF(SSL_CIPHER) *supportedCiphers = q_SSL_get_ciphers(connection);
|
|
|
|
for (int i = 0; i < q_sk_SSL_CIPHER_num(supportedCiphers); ++i) {
|
|
|
|
if (SSL_CIPHER *cipher = q_sk_SSL_CIPHER_value(supportedCiphers, i)) {
|
|
|
|
QSslCipher ciph = QSslSocketBackendPrivate::QSslCipher_from_SSL_CIPHER(cipher);
|
|
|
|
if (!ciph.isNull()) {
|
|
|
|
// Unconditionally exclude ADH and AECDH ciphers since they offer no MITM protection
|
|
|
|
if (!ciph.name().toLower().startsWith(QLatin1String("adh")) &&
|
|
|
|
!ciph.name().toLower().startsWith(QLatin1String("exp-adh")) &&
|
|
|
|
!ciph.name().toLower().startsWith(QLatin1String("aecdh"))) {
|
|
|
|
ciphers << ciph;
|
|
|
|
|
|
|
|
if (ciph.usedBits() >= 128)
|
|
|
|
defaultCiphers << ciph;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Defined in qsslsocket.cpp
|
|
|
|
void q_setDefaultDtlsCiphers(const QList<QSslCipher> &ciphers);
|
|
|
|
|
2011-11-15 21:58:05 +00:00
|
|
|
long QSslSocketBackendPrivate::setupOpenSslOptions(QSsl::SslProtocol protocol, QSsl::SslOptions sslOptions)
|
|
|
|
{
|
|
|
|
long options;
|
2019-11-13 09:37:36 +00:00
|
|
|
switch (protocol) {
|
|
|
|
case QSsl::SecureProtocols:
|
|
|
|
case QSsl::TlsV1_0OrLater:
|
|
|
|
options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
|
|
|
|
break;
|
|
|
|
case QSsl::TlsV1_1OrLater:
|
|
|
|
options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
|
|
|
|
break;
|
|
|
|
case QSsl::TlsV1_2OrLater:
|
|
|
|
options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
|
|
|
|
break;
|
|
|
|
case QSsl::TlsV1_3OrLater:
|
|
|
|
options = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
|
|
|
|
break;
|
|
|
|
default:
|
2011-11-15 21:58:05 +00:00
|
|
|
options = SSL_OP_ALL;
|
2019-11-13 09:37:36 +00:00
|
|
|
}
|
2011-11-15 21:58:05 +00:00
|
|
|
|
|
|
|
// This option is disabled by default, so we need to be able to clear it
|
|
|
|
if (sslOptions & QSsl::SslOptionDisableEmptyFragments)
|
|
|
|
options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
|
|
|
else
|
|
|
|
options &= ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
|
|
|
|
|
|
|
|
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
|
|
|
|
// This option is disabled by default, so we need to be able to clear it
|
|
|
|
if (sslOptions & QSsl::SslOptionDisableLegacyRenegotiation)
|
|
|
|
options &= ~SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
|
|
|
|
else
|
|
|
|
options |= SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef SSL_OP_NO_TICKET
|
|
|
|
if (sslOptions & QSsl::SslOptionDisableSessionTickets)
|
|
|
|
options |= SSL_OP_NO_TICKET;
|
|
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_COMPRESSION
|
|
|
|
if (sslOptions & QSsl::SslOptionDisableCompression)
|
|
|
|
options |= SSL_OP_NO_COMPRESSION;
|
|
|
|
#endif
|
|
|
|
|
2015-04-18 10:34:26 +00:00
|
|
|
if (!(sslOptions & QSsl::SslOptionDisableServerCipherPreference))
|
|
|
|
options |= SSL_OP_CIPHER_SERVER_PREFERENCE;
|
|
|
|
|
2011-11-15 21:58:05 +00:00
|
|
|
return options;
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
bool QSslSocketBackendPrivate::initSslContext()
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
2019-01-30 13:11:07 +00:00
|
|
|
// If no external context was set (e.g. by QHttpNetworkConnection) we will
|
|
|
|
// create a default context
|
2013-04-17 15:42:32 +00:00
|
|
|
if (!sslContextPointer) {
|
|
|
|
// create a deep copy of our configuration
|
|
|
|
QSslConfigurationPrivate *configurationCopy = new QSslConfigurationPrivate(configuration);
|
2019-06-10 09:08:29 +00:00
|
|
|
configurationCopy->ref.storeRelaxed(0); // the QSslConfiguration constructor refs up
|
2014-04-09 06:08:01 +00:00
|
|
|
sslContextPointer = QSslContext::sharedFromConfiguration(mode, configurationCopy, allowRootCertOnDemandLoading);
|
2013-04-17 15:42:32 +00:00
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2012-12-21 13:02:38 +00:00
|
|
|
if (sslContextPointer->error() != QSslError::NoError) {
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError, sslContextPointer->errorString());
|
2012-12-21 13:02:38 +00:00
|
|
|
sslContextPointer.clear(); // deletes the QSslContext
|
2011-04-27 10:05:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create and initialize SSL session
|
2012-12-21 13:02:38 +00:00
|
|
|
if (!(ssl = sslContextPointer->createSsl())) {
|
2011-04-27 10:05:43 +00:00
|
|
|
// ### Bad error code
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Error creating SSL session, %1").arg(getErrorsFromOpenSsl()));
|
2011-04-27 10:05:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-11-13 09:37:36 +00:00
|
|
|
if (configuration.protocol != QSsl::UnknownProtocol && mode == QSslSocket::SslClientMode) {
|
2011-04-27 10:05:43 +00:00
|
|
|
// Set server hostname on TLS extension. RFC4366 section 3.1 requires it in ACE format.
|
|
|
|
QString tlsHostName = verificationPeerName.isEmpty() ? q->peerName() : verificationPeerName;
|
|
|
|
if (tlsHostName.isEmpty())
|
|
|
|
tlsHostName = hostName;
|
|
|
|
QByteArray ace = QUrl::toAce(tlsHostName);
|
|
|
|
// only send the SNI header if the URL is valid and not an IP
|
2011-10-19 09:40:57 +00:00
|
|
|
if (!ace.isEmpty()
|
|
|
|
&& !QHostAddress().setAddress(tlsHostName)
|
|
|
|
&& !(configuration.sslOptions & QSsl::SslOptionDisableServerNameIndication)) {
|
2016-03-12 16:47:14 +00:00
|
|
|
// We don't send the trailing dot from the host header if present see
|
|
|
|
// https://tools.ietf.org/html/rfc6066#section-3
|
|
|
|
if (ace.endsWith('.'))
|
|
|
|
ace.chop(1);
|
2011-06-27 16:12:46 +00:00
|
|
|
if (!q_SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, ace.data()))
|
2014-12-08 12:35:47 +00:00
|
|
|
qCWarning(lcSsl, "could not set SSL_CTRL_SET_TLSEXT_HOSTNAME, Server Name Indication disabled");
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Clear the session.
|
|
|
|
errorList.clear();
|
|
|
|
|
|
|
|
// Initialize memory BIOs for encryption and decryption.
|
|
|
|
readBio = q_BIO_new(q_BIO_s_mem());
|
|
|
|
writeBio = q_BIO_new(q_BIO_s_mem());
|
|
|
|
if (!readBio || !writeBio) {
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Error creating SSL session: %1").arg(getErrorsFromOpenSsl()));
|
2011-04-27 10:05:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assign the bios.
|
|
|
|
q_SSL_set_bio(ssl, readBio, writeBio);
|
|
|
|
|
|
|
|
if (mode == QSslSocket::SslClientMode)
|
|
|
|
q_SSL_set_connect_state(ssl);
|
|
|
|
else
|
|
|
|
q_SSL_set_accept_state(ssl);
|
|
|
|
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
q_SSL_set_ex_data(ssl, s_indexForSSLExtraData, this);
|
2014-10-15 13:13:47 +00:00
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
#ifndef OPENSSL_NO_PSK
|
2014-10-15 13:13:47 +00:00
|
|
|
// Set the client callback for PSK
|
2019-09-27 11:04:54 +00:00
|
|
|
if (mode == QSslSocket::SslClientMode)
|
|
|
|
q_SSL_set_psk_client_callback(ssl, &q_ssl_psk_client_callback);
|
|
|
|
else if (mode == QSslSocket::SslServerMode)
|
|
|
|
q_SSL_set_psk_server_callback(ssl, &q_ssl_psk_server_callback);
|
|
|
|
|
2018-12-13 14:39:26 +00:00
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10101006L
|
|
|
|
// Set the client callback for TLSv1.3 PSK
|
|
|
|
if (mode == QSslSocket::SslClientMode
|
|
|
|
&& QSslSocket::sslLibraryBuildVersionNumber() >= 0x10101006L) {
|
|
|
|
q_SSL_set_psk_use_session_callback(ssl, &q_ssl_psk_use_session_callback);
|
|
|
|
}
|
|
|
|
#endif // openssl version >= 0x10101006L
|
2014-10-15 13:13:47 +00:00
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
#endif // OPENSSL_NO_PSK
|
|
|
|
|
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
#if QT_CONFIG(ocsp)
|
|
|
|
if (configuration.ocspStaplingEnabled) {
|
|
|
|
if (mode == QSslSocket::SslServerMode) {
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError,
|
|
|
|
QSslSocket::tr("Server-side QSslSocket does not support OCSP stapling"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (q_SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp) != 1) {
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Failed to enable OCSP stapling"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-11-13 14:25:25 +00:00
|
|
|
|
|
|
|
ocspResponseDer.clear();
|
|
|
|
auto responsePos = configuration.backendConfig.find("Qt-OCSP-response");
|
|
|
|
if (responsePos != configuration.backendConfig.end()) {
|
|
|
|
// This is our private, undocumented 'API' we use for the auto-testing of
|
|
|
|
// OCSP-stapling. It must be a der-encoded OCSP response, presumably set
|
|
|
|
// by tst_QOcsp.
|
|
|
|
const QVariant data(responsePos.value());
|
|
|
|
if (data.canConvert<QByteArray>())
|
|
|
|
ocspResponseDer = data.toByteArray();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ocspResponseDer.size()) {
|
|
|
|
if (mode != QSslSocket::SslServerMode) {
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslInvalidUserDataError,
|
|
|
|
QSslSocket::tr("Client-side sockets do not send OCSP responses"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2018-10-25 08:44:16 +00:00
|
|
|
#endif // ocsp
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-01-30 15:52:27 +00:00
|
|
|
void QSslSocketBackendPrivate::destroySslContext()
|
|
|
|
{
|
|
|
|
if (ssl) {
|
2020-04-13 18:31:34 +00:00
|
|
|
if (!q_SSL_in_init(ssl) && !systemOrSslErrorDetected) {
|
|
|
|
// We do not send a shutdown alert here. Just mark the session as
|
|
|
|
// resumable for qhttpnetworkconnection's "optimization", otherwise
|
|
|
|
// OpenSSL won't start a session resumption.
|
|
|
|
if (q_SSL_shutdown(ssl) != 1) {
|
|
|
|
// Some error may be queued, clear it.
|
|
|
|
const auto errors = getErrorsFromOpenSsl();
|
|
|
|
Q_UNUSED(errors);
|
|
|
|
}
|
|
|
|
}
|
2012-01-30 15:52:27 +00:00
|
|
|
q_SSL_free(ssl);
|
2018-08-07 08:29:46 +00:00
|
|
|
ssl = nullptr;
|
2012-01-30 15:52:27 +00:00
|
|
|
}
|
2012-12-21 13:02:38 +00:00
|
|
|
sslContextPointer.clear();
|
2012-01-30 15:52:27 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Does the minimum amount of initialization to determine whether SSL
|
|
|
|
is supported or not.
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool QSslSocketPrivate::supportsSsl()
|
|
|
|
{
|
|
|
|
return ensureLibraryLoaded();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Returns the version number of the SSL library in use. Note that
|
|
|
|
this is the version of the library in use at run-time, not compile
|
|
|
|
time.
|
|
|
|
*/
|
|
|
|
long QSslSocketPrivate::sslLibraryVersionNumber()
|
|
|
|
{
|
|
|
|
if (!supportsSsl())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return q_OpenSSL_version_num();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Returns the version string of the SSL library in use. Note that
|
|
|
|
this is the version of the library in use at run-time, not compile
|
|
|
|
time. If no SSL support is available then this will return an empty value.
|
|
|
|
*/
|
|
|
|
QString QSslSocketPrivate::sslLibraryVersionString()
|
|
|
|
{
|
|
|
|
if (!supportsSsl())
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
const char *versionString = q_OpenSSL_version(OPENSSL_VERSION);
|
|
|
|
if (!versionString)
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
return QString::fromLatin1(versionString);
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Declared static in QSslSocketPrivate, makes sure the SSL libraries have
|
|
|
|
been initialized.
|
|
|
|
*/
|
|
|
|
void QSslSocketPrivate::ensureInitialized()
|
|
|
|
{
|
|
|
|
if (!supportsSsl())
|
|
|
|
return;
|
|
|
|
|
|
|
|
ensureCiphersAndCertsLoaded();
|
|
|
|
}
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Returns the version number of the SSL library in use at compile
|
|
|
|
time.
|
|
|
|
*/
|
2014-03-10 16:39:46 +00:00
|
|
|
long QSslSocketPrivate::sslLibraryBuildVersionNumber()
|
|
|
|
{
|
|
|
|
return OPENSSL_VERSION_NUMBER;
|
|
|
|
}
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Returns the version string of the SSL library in use at compile
|
|
|
|
time.
|
|
|
|
*/
|
2014-03-10 16:39:46 +00:00
|
|
|
QString QSslSocketPrivate::sslLibraryBuildVersionString()
|
|
|
|
{
|
2015-05-23 11:11:09 +00:00
|
|
|
// Using QStringLiteral to store the version string as unicode and
|
|
|
|
// avoid false positives from Google searching the playstore for old
|
|
|
|
// SSL versions. See QTBUG-46265
|
|
|
|
return QStringLiteral(OPENSSL_VERSION_TEXT);
|
2014-03-10 16:39:46 +00:00
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Declared static in QSslSocketPrivate, backend-dependent loading of
|
|
|
|
application-wide global ciphers.
|
|
|
|
*/
|
|
|
|
void QSslSocketPrivate::resetDefaultCiphers()
|
|
|
|
{
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
SSL_CTX *myCtx = q_SSL_CTX_new(q_TLS_client_method());
|
2018-10-30 09:43:51 +00:00
|
|
|
// Note, we assert, not just silently return/bail out early:
|
|
|
|
// this should never happen and problems with OpenSSL's initialization
|
|
|
|
// must be caught before this (see supportsSsl()).
|
|
|
|
Q_ASSERT(myCtx);
|
2011-04-27 10:05:43 +00:00
|
|
|
SSL *mySsl = q_SSL_new(myCtx);
|
2018-10-30 09:43:51 +00:00
|
|
|
Q_ASSERT(mySsl);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
QList<QSslCipher> ciphers;
|
2014-01-17 21:23:20 +00:00
|
|
|
QList<QSslCipher> defaultCiphers;
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2018-02-19 12:46:21 +00:00
|
|
|
q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
q_SSL_CTX_free(myCtx);
|
|
|
|
q_SSL_free(mySsl);
|
|
|
|
|
|
|
|
setDefaultSupportedCiphers(ciphers);
|
2014-01-17 21:23:20 +00:00
|
|
|
setDefaultCiphers(defaultCiphers);
|
2018-02-19 12:46:21 +00:00
|
|
|
|
2018-06-25 14:30:36 +00:00
|
|
|
#if QT_CONFIG(dtls)
|
2018-02-19 12:46:21 +00:00
|
|
|
ciphers.clear();
|
|
|
|
defaultCiphers.clear();
|
|
|
|
myCtx = q_SSL_CTX_new(q_DTLS_client_method());
|
|
|
|
if (myCtx) {
|
|
|
|
mySsl = q_SSL_new(myCtx);
|
|
|
|
if (mySsl) {
|
|
|
|
q_loadCiphersForConnection(mySsl, ciphers, defaultCiphers);
|
|
|
|
q_setDefaultDtlsCiphers(defaultCiphers);
|
|
|
|
q_SSL_free(mySsl);
|
|
|
|
}
|
|
|
|
q_SSL_CTX_free(myCtx);
|
|
|
|
}
|
2018-06-25 14:30:36 +00:00
|
|
|
#endif // dtls
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
2014-09-03 09:12:12 +00:00
|
|
|
void QSslSocketPrivate::resetDefaultEllipticCurves()
|
|
|
|
{
|
2020-06-22 10:25:41 +00:00
|
|
|
QList<QSslEllipticCurve> curves;
|
2014-09-03 09:12:12 +00:00
|
|
|
|
|
|
|
#ifndef OPENSSL_NO_EC
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
const size_t curveCount = q_EC_get_builtin_curves(nullptr, 0);
|
2014-09-03 09:12:12 +00:00
|
|
|
|
|
|
|
QVarLengthArray<EC_builtin_curve> builtinCurves(static_cast<int>(curveCount));
|
|
|
|
|
|
|
|
if (q_EC_get_builtin_curves(builtinCurves.data(), curveCount) == curveCount) {
|
2015-06-01 13:05:41 +00:00
|
|
|
curves.reserve(int(curveCount));
|
2014-09-03 09:12:12 +00:00
|
|
|
for (size_t i = 0; i < curveCount; ++i) {
|
|
|
|
QSslEllipticCurve curve;
|
2015-03-05 23:23:53 +00:00
|
|
|
curve.id = builtinCurves[int(i)].nid;
|
2014-09-03 09:12:12 +00:00
|
|
|
curves.append(curve);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // OPENSSL_NO_EC
|
|
|
|
|
|
|
|
// set the list of supported ECs, but not the list
|
|
|
|
// of *default* ECs. OpenSSL doesn't like forcing an EC for the wrong
|
|
|
|
// ciphersuite, so don't try it -- leave the empty list to mean
|
|
|
|
// "the implementation will choose the most suitable one".
|
|
|
|
setDefaultSupportedEllipticCurves(curves);
|
|
|
|
}
|
|
|
|
|
2015-11-16 14:06:15 +00:00
|
|
|
#ifndef Q_OS_DARWIN // Apple implementation in qsslsocket_mac_shared.cpp
|
2011-04-27 10:05:43 +00:00
|
|
|
QList<QSslCertificate> QSslSocketPrivate::systemCaCertificates()
|
|
|
|
{
|
|
|
|
ensureInitialized();
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
|
|
|
QElapsedTimer timer;
|
|
|
|
timer.start();
|
|
|
|
#endif
|
|
|
|
QList<QSslCertificate> systemCerts;
|
2015-11-16 14:06:15 +00:00
|
|
|
#if defined(Q_OS_WIN)
|
2018-11-14 14:16:00 +00:00
|
|
|
HCERTSTORE hSystemStore;
|
|
|
|
hSystemStore = CertOpenSystemStoreW(0, L"ROOT");
|
|
|
|
if (hSystemStore) {
|
|
|
|
PCCERT_CONTEXT pc = nullptr;
|
|
|
|
while (1) {
|
|
|
|
pc = CertFindCertificateInStore(hSystemStore, X509_ASN_ENCODING, 0, CERT_FIND_ANY, nullptr, pc);
|
|
|
|
if (!pc)
|
|
|
|
break;
|
|
|
|
QByteArray der(reinterpret_cast<const char *>(pc->pbCertEncoded),
|
|
|
|
static_cast<int>(pc->cbCertEncoded));
|
|
|
|
QSslCertificate cert(der, QSsl::Der);
|
|
|
|
systemCerts.append(cert);
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
2018-11-14 14:16:00 +00:00
|
|
|
CertCloseStore(hSystemStore, 0);
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
2011-11-01 13:20:19 +00:00
|
|
|
#elif defined(Q_OS_UNIX)
|
2011-04-27 10:05:43 +00:00
|
|
|
QSet<QString> certFiles;
|
|
|
|
QDir currentDir;
|
|
|
|
QStringList nameFilters;
|
2013-07-25 12:40:06 +00:00
|
|
|
QList<QByteArray> directories;
|
|
|
|
QSsl::EncodingFormat platformEncodingFormat;
|
|
|
|
# ifndef Q_OS_ANDROID
|
|
|
|
directories = unixRootCertDirectories();
|
2011-04-27 10:05:43 +00:00
|
|
|
nameFilters << QLatin1String("*.pem") << QLatin1String("*.crt");
|
2013-07-25 12:40:06 +00:00
|
|
|
platformEncodingFormat = QSsl::Pem;
|
2013-03-04 09:16:42 +00:00
|
|
|
# else
|
2013-07-25 12:40:06 +00:00
|
|
|
// Q_OS_ANDROID
|
|
|
|
QByteArray ministroPath = qgetenv("MINISTRO_SSL_CERTS_PATH"); // Set by Ministro
|
|
|
|
directories << ministroPath;
|
|
|
|
nameFilters << QLatin1String("*.der");
|
|
|
|
platformEncodingFormat = QSsl::Der;
|
2017-06-02 09:53:23 +00:00
|
|
|
# ifndef Q_OS_ANDROID_EMBEDDED
|
2013-07-25 12:40:06 +00:00
|
|
|
if (ministroPath.isEmpty()) {
|
|
|
|
QList<QByteArray> certificateData = fetchSslCertificateData();
|
|
|
|
for (int i = 0; i < certificateData.size(); ++i) {
|
|
|
|
systemCerts.append(QSslCertificate::fromData(certificateData.at(i), QSsl::Der));
|
|
|
|
}
|
|
|
|
} else
|
2017-06-02 09:53:23 +00:00
|
|
|
# endif //Q_OS_ANDROID_EMBEDDED
|
2013-07-25 12:40:06 +00:00
|
|
|
# endif //Q_OS_ANDROID
|
|
|
|
{
|
|
|
|
currentDir.setNameFilters(nameFilters);
|
|
|
|
for (int a = 0; a < directories.count(); a++) {
|
|
|
|
currentDir.setPath(QLatin1String(directories.at(a)));
|
|
|
|
QDirIterator it(currentDir);
|
|
|
|
while (it.hasNext()) {
|
|
|
|
it.next();
|
|
|
|
// use canonical path here to not load the same certificate twice if symlinked
|
|
|
|
certFiles.insert(it.fileInfo().canonicalFilePath());
|
|
|
|
}
|
|
|
|
}
|
2016-03-04 07:51:12 +00:00
|
|
|
for (const QString& file : qAsConst(certFiles))
|
|
|
|
systemCerts.append(QSslCertificate::fromPath(file, platformEncodingFormat));
|
2013-03-04 09:16:42 +00:00
|
|
|
# ifndef Q_OS_ANDROID
|
2013-07-25 12:40:06 +00:00
|
|
|
systemCerts.append(QSslCertificate::fromPath(QLatin1String("/etc/pki/tls/certs/ca-bundle.crt"), QSsl::Pem)); // Fedora, Mandriva
|
|
|
|
systemCerts.append(QSslCertificate::fromPath(QLatin1String("/usr/local/share/certs/ca-root-nss.crt"), QSsl::Pem)); // FreeBSD's ca_root_nss
|
2013-03-04 09:16:42 +00:00
|
|
|
# endif
|
2013-07-25 12:40:06 +00:00
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "systemCaCertificates retrieval time " << timer.elapsed() << "ms";
|
|
|
|
qCDebug(lcSsl) << "imported " << systemCerts.count() << " certificates";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
return systemCerts;
|
|
|
|
}
|
2015-11-16 14:06:15 +00:00
|
|
|
#endif // Q_OS_DARWIN
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::startClientEncryption()
|
|
|
|
{
|
|
|
|
if (!initSslContext()) {
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Unable to init SSL Context: %1").arg(getErrorsFromOpenSsl()));
|
2011-04-27 10:05:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start connecting. This will place outgoing data in the BIO, so we
|
|
|
|
// follow up with calling transmit().
|
|
|
|
startHandshake();
|
|
|
|
transmit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::startServerEncryption()
|
|
|
|
{
|
|
|
|
if (!initSslContext()) {
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Unable to init SSL Context: %1").arg(getErrorsFromOpenSsl()));
|
2011-04-27 10:05:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start connecting. This will place outgoing data in the BIO, so we
|
|
|
|
// follow up with calling transmit().
|
|
|
|
startHandshake();
|
|
|
|
transmit();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
\internal
|
|
|
|
|
|
|
|
Transmits encrypted data between the BIOs and the socket.
|
|
|
|
*/
|
|
|
|
void QSslSocketBackendPrivate::transmit()
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
2018-05-08 10:35:46 +00:00
|
|
|
using ScopedBool = QScopedValueRollback<bool>;
|
|
|
|
|
|
|
|
if (inSetAndEmitError)
|
|
|
|
return;
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
// If we don't have any SSL context, don't bother transmitting.
|
|
|
|
if (!ssl)
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool transmitting;
|
|
|
|
do {
|
|
|
|
transmitting = false;
|
|
|
|
|
|
|
|
// If the connection is secure, we can transfer data from the write
|
|
|
|
// buffer (in plain text) to the write BIO through SSL_write.
|
|
|
|
if (connectionEncrypted && !writeBuffer.isEmpty()) {
|
|
|
|
qint64 totalBytesWritten = 0;
|
|
|
|
int nextDataBlockSize;
|
|
|
|
while ((nextDataBlockSize = writeBuffer.nextDataBlockSize()) > 0) {
|
|
|
|
int writtenBytes = q_SSL_write(ssl, writeBuffer.readPointer(), nextDataBlockSize);
|
|
|
|
if (writtenBytes <= 0) {
|
2013-07-25 17:22:01 +00:00
|
|
|
int error = q_SSL_get_error(ssl, writtenBytes);
|
|
|
|
//write can result in a want_write_error - not an error - continue transmitting
|
|
|
|
if (error == SSL_ERROR_WANT_WRITE) {
|
|
|
|
transmitting = true;
|
|
|
|
break;
|
|
|
|
} else if (error == SSL_ERROR_WANT_READ) {
|
|
|
|
//write can result in a want_read error, possibly due to renegotiation - not an error - stop transmitting
|
|
|
|
transmitting = false;
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
// ### Better error handling.
|
2018-05-08 10:35:46 +00:00
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Unable to write data: %1").arg(
|
|
|
|
getErrorsFromOpenSsl()));
|
2013-07-25 17:22:01 +00:00
|
|
|
return;
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encrypted" << writtenBytes << "bytes";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
writeBuffer.free(writtenBytes);
|
|
|
|
totalBytesWritten += writtenBytes;
|
|
|
|
|
|
|
|
if (writtenBytes < nextDataBlockSize) {
|
|
|
|
// break out of the writing loop and try again after we had read
|
|
|
|
transmitting = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (totalBytesWritten > 0) {
|
|
|
|
// Don't emit bytesWritten() recursively.
|
|
|
|
if (!emittedBytesWritten) {
|
|
|
|
emittedBytesWritten = true;
|
|
|
|
emit q->bytesWritten(totalBytesWritten);
|
|
|
|
emittedBytesWritten = false;
|
|
|
|
}
|
2015-09-14 12:53:13 +00:00
|
|
|
emit q->channelBytesWritten(0, totalBytesWritten);
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we've got any data to be written to the socket.
|
|
|
|
QVarLengthArray<char, 4096> data;
|
|
|
|
int pendingBytes;
|
2018-09-27 12:20:46 +00:00
|
|
|
while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0
|
|
|
|
&& plainSocket->openMode() != QIODevice::NotOpen) {
|
2011-04-27 10:05:43 +00:00
|
|
|
// Read encrypted data from the write BIO into a buffer.
|
|
|
|
data.resize(pendingBytes);
|
|
|
|
int encryptedBytesRead = q_BIO_read(writeBio, data.data(), pendingBytes);
|
|
|
|
|
|
|
|
// Write encrypted data from the buffer to the socket.
|
2011-12-19 15:10:45 +00:00
|
|
|
qint64 actualWritten = plainSocket->write(data.constData(), encryptedBytesRead);
|
2011-04-27 10:05:43 +00:00
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: wrote" << encryptedBytesRead << "encrypted bytes to the socket" << actualWritten << "actual.";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
2011-12-19 15:10:45 +00:00
|
|
|
if (actualWritten < 0) {
|
|
|
|
//plain socket write fails if it was in the pending close state.
|
2018-05-08 10:35:46 +00:00
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
2020-02-07 13:43:07 +00:00
|
|
|
setErrorAndEmit(plainSocket->error(), plainSocket->errorString());
|
2011-12-19 15:10:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
transmitting = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if we've got any data to be read from the socket.
|
2012-05-30 15:03:57 +00:00
|
|
|
if (!connectionEncrypted || !readBufferMaxSize || buffer.size() < readBufferMaxSize)
|
2011-04-27 10:05:43 +00:00
|
|
|
while ((pendingBytes = plainSocket->bytesAvailable()) > 0) {
|
|
|
|
// Read encrypted data from the socket into a buffer.
|
|
|
|
data.resize(pendingBytes);
|
|
|
|
// just peek() here because q_BIO_write could write less data than expected
|
|
|
|
int encryptedBytesRead = plainSocket->peek(data.data(), pendingBytes);
|
2012-05-30 15:03:57 +00:00
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: read" << encryptedBytesRead << "encrypted bytes from the socket";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
// Write encrypted data from the buffer into the read BIO.
|
|
|
|
int writtenToBio = q_BIO_write(readBio, data.constData(), encryptedBytesRead);
|
|
|
|
|
2017-05-08 17:07:37 +00:00
|
|
|
// Throw away the results.
|
2011-04-27 10:05:43 +00:00
|
|
|
if (writtenToBio > 0) {
|
2017-05-08 17:07:37 +00:00
|
|
|
plainSocket->skip(writtenToBio);
|
2011-04-27 10:05:43 +00:00
|
|
|
} else {
|
|
|
|
// ### Better error handling.
|
2018-05-08 10:35:46 +00:00
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
2015-07-10 14:10:44 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Unable to decrypt data: %1").arg(
|
|
|
|
getErrorsFromOpenSsl()));
|
2011-04-27 10:05:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
transmitting = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the connection isn't secured yet, this is the time to retry the
|
|
|
|
// connect / accept.
|
|
|
|
if (!connectionEncrypted) {
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: testing encryption";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
if (startHandshake()) {
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encryption established";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
connectionEncrypted = true;
|
|
|
|
transmitting = true;
|
|
|
|
} else if (plainSocket->state() != QAbstractSocket::ConnectedState) {
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: connection lost";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
break;
|
2012-01-20 12:55:15 +00:00
|
|
|
} else if (paused) {
|
|
|
|
// just wait until the user continues
|
|
|
|
return;
|
2011-04-27 10:05:43 +00:00
|
|
|
} else {
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: encryption not done yet";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the request is small and the remote host closes the transmission
|
|
|
|
// after sending, there's a chance that startHandshake() will already
|
|
|
|
// have triggered a shutdown.
|
|
|
|
if (!ssl)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// We always read everything from the SSL decryption buffers, even if
|
|
|
|
// we have a readBufferMaxSize. There's no point in leaving data there
|
|
|
|
// just so that readBuffer.size() == readBufferMaxSize.
|
|
|
|
int readBytes = 0;
|
2017-07-26 10:39:11 +00:00
|
|
|
const int bytesToRead = 4096;
|
2011-04-27 10:05:43 +00:00
|
|
|
do {
|
2018-09-27 12:20:46 +00:00
|
|
|
if (readChannelCount == 0) {
|
|
|
|
// The read buffer is deallocated, don't try resize or write to it.
|
|
|
|
break;
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
// Don't use SSL_pending(). It's very unreliable.
|
2017-07-26 10:39:11 +00:00
|
|
|
readBytes = q_SSL_read(ssl, buffer.reserve(bytesToRead), bytesToRead);
|
|
|
|
if (readBytes > 0) {
|
2011-04-27 10:05:43 +00:00
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: decrypted" << readBytes << "bytes";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
2017-07-26 10:39:11 +00:00
|
|
|
buffer.chop(bytesToRead - readBytes);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
if (readyReadEmittedPointer)
|
|
|
|
*readyReadEmittedPointer = true;
|
|
|
|
emit q->readyRead();
|
2015-09-14 12:53:13 +00:00
|
|
|
emit q->channelReadyRead(0);
|
2011-04-27 10:05:43 +00:00
|
|
|
transmitting = true;
|
|
|
|
continue;
|
|
|
|
}
|
2017-07-26 10:39:11 +00:00
|
|
|
buffer.chop(bytesToRead);
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
// Error.
|
|
|
|
switch (q_SSL_get_error(ssl, readBytes)) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
// Out of data.
|
|
|
|
break;
|
|
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
|
|
// The remote host closed the connection.
|
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2014-12-08 12:35:47 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::transmit: remote disconnect";
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
2013-04-04 09:30:43 +00:00
|
|
|
shutdown = true; // the other side shut down, make sure we do not send shutdown ourselves
|
2018-05-08 10:35:46 +00:00
|
|
|
{
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
setErrorAndEmit(QAbstractSocket::RemoteHostClosedError,
|
|
|
|
QSslSocket::tr("The TLS/SSL connection has been closed"));
|
|
|
|
}
|
2013-04-04 09:30:43 +00:00
|
|
|
return;
|
2011-04-27 10:05:43 +00:00
|
|
|
case SSL_ERROR_SYSCALL: // some IO error
|
|
|
|
case SSL_ERROR_SSL: // error in the SSL library
|
|
|
|
// we do not know exactly what the error is, nor whether we can recover from it,
|
|
|
|
// so just return to prevent an endless loop in the outer "while" statement
|
2020-04-13 18:31:34 +00:00
|
|
|
systemOrSslErrorDetected = true;
|
2018-05-08 10:35:46 +00:00
|
|
|
{
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl()));
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
return;
|
|
|
|
default:
|
|
|
|
// SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: can only happen with a
|
|
|
|
// BIO_s_connect() or BIO_s_accept(), which we do not call.
|
|
|
|
// SSL_ERROR_WANT_X509_LOOKUP: can only happen with a
|
|
|
|
// SSL_CTX_set_client_cert_cb(), which we do not call.
|
|
|
|
// So this default case should never be triggered.
|
2018-05-08 10:35:46 +00:00
|
|
|
{
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslInternalError,
|
|
|
|
QSslSocket::tr("Error while reading: %1").arg(getErrorsFromOpenSsl()));
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} while (ssl && readBytes > 0);
|
2012-12-21 13:02:38 +00:00
|
|
|
} while (ssl && transmitting);
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
2018-02-19 14:19:16 +00:00
|
|
|
QSslError _q_OpenSSL_to_QSslError(int errorCode, const QSslCertificate &cert)
|
2011-04-27 10:05:43 +00:00
|
|
|
{
|
|
|
|
QSslError error;
|
|
|
|
switch (errorCode) {
|
|
|
|
case X509_V_OK:
|
|
|
|
// X509_V_OK is also reported if the peer had no certificate.
|
|
|
|
break;
|
|
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
|
|
|
|
error = QSslError(QSslError::UnableToGetIssuerCertificate, cert); break;
|
|
|
|
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
|
|
|
|
error = QSslError(QSslError::UnableToDecryptCertificateSignature, cert); break;
|
|
|
|
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
|
|
|
|
error = QSslError(QSslError::UnableToDecodeIssuerPublicKey, cert); break;
|
|
|
|
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
|
|
|
|
error = QSslError(QSslError::CertificateSignatureFailed, cert); break;
|
|
|
|
case X509_V_ERR_CERT_NOT_YET_VALID:
|
|
|
|
error = QSslError(QSslError::CertificateNotYetValid, cert); break;
|
|
|
|
case X509_V_ERR_CERT_HAS_EXPIRED:
|
|
|
|
error = QSslError(QSslError::CertificateExpired, cert); break;
|
|
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
|
|
|
|
error = QSslError(QSslError::InvalidNotBeforeField, cert); break;
|
|
|
|
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
|
|
|
|
error = QSslError(QSslError::InvalidNotAfterField, cert); break;
|
|
|
|
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
|
|
|
|
error = QSslError(QSslError::SelfSignedCertificate, cert); break;
|
|
|
|
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
|
|
|
|
error = QSslError(QSslError::SelfSignedCertificateInChain, cert); break;
|
|
|
|
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
|
|
|
|
error = QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert); break;
|
|
|
|
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
|
|
|
|
error = QSslError(QSslError::UnableToVerifyFirstCertificate, cert); break;
|
|
|
|
case X509_V_ERR_CERT_REVOKED:
|
|
|
|
error = QSslError(QSslError::CertificateRevoked, cert); break;
|
|
|
|
case X509_V_ERR_INVALID_CA:
|
|
|
|
error = QSslError(QSslError::InvalidCaCertificate, cert); break;
|
|
|
|
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
|
|
|
|
error = QSslError(QSslError::PathLengthExceeded, cert); break;
|
|
|
|
case X509_V_ERR_INVALID_PURPOSE:
|
|
|
|
error = QSslError(QSslError::InvalidPurpose, cert); break;
|
|
|
|
case X509_V_ERR_CERT_UNTRUSTED:
|
|
|
|
error = QSslError(QSslError::CertificateUntrusted, cert); break;
|
|
|
|
case X509_V_ERR_CERT_REJECTED:
|
|
|
|
error = QSslError(QSslError::CertificateRejected, cert); break;
|
|
|
|
default:
|
|
|
|
error = QSslError(QSslError::UnspecifiedError, cert); break;
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2018-06-27 09:38:53 +00:00
|
|
|
QString QSslSocketBackendPrivate::msgErrorsDuringHandshake()
|
|
|
|
{
|
|
|
|
return QSslSocket::tr("Error during SSL handshake: %1")
|
|
|
|
.arg(QSslSocketBackendPrivate::getErrorsFromOpenSsl());
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
bool QSslSocketBackendPrivate::startHandshake()
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
|
|
|
// Check if the connection has been established. Get all errors from the
|
|
|
|
// verification stage.
|
2018-05-08 10:35:46 +00:00
|
|
|
|
|
|
|
using ScopedBool = QScopedValueRollback<bool>;
|
|
|
|
|
|
|
|
if (inSetAndEmitError)
|
|
|
|
return false;
|
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
pendingFatalAlert = false;
|
|
|
|
errorsReportedFromCallback = false;
|
2020-06-22 10:25:41 +00:00
|
|
|
QList<QSslErrorEntry> lastErrors;
|
2019-10-30 10:49:07 +00:00
|
|
|
q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + errorOffsetInExData, &lastErrors);
|
|
|
|
|
|
|
|
// SSL_set_ex_data can fail, but see the callback's code - we handle this there.
|
|
|
|
q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + socketOffsetInExData, this);
|
|
|
|
q_SSL_set_info_callback(ssl, qt_AlertInfoCallback);
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
int result = (mode == QSslSocket::SslClientMode) ? q_SSL_connect(ssl) : q_SSL_accept(ssl);
|
2019-10-30 10:49:07 +00:00
|
|
|
q_SSL_set_ex_data(ssl, s_indexForSSLExtraData + errorOffsetInExData, nullptr);
|
|
|
|
// Note, unlike errors as external data on SSL object, we do not unset
|
|
|
|
// a callback/ex-data if alert notifications are enabled: an alert can
|
|
|
|
// arrive after the handshake, for example, this happens when the server
|
|
|
|
// does not find a ClientCert or does not like it.
|
2011-04-27 10:05:43 +00:00
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
if (!lastErrors.isEmpty() || errorsReportedFromCallback)
|
2015-02-03 14:32:53 +00:00
|
|
|
storePeerCertificates();
|
2019-10-30 10:49:07 +00:00
|
|
|
|
|
|
|
if (!errorsReportedFromCallback) {
|
|
|
|
for (const auto ¤tError : qAsConst(lastErrors)) {
|
|
|
|
emit q->peerVerifyError(_q_OpenSSL_to_QSslError(currentError.code,
|
|
|
|
configuration.peerCertificateChain.value(currentError.depth)));
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
break;
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
errorList << lastErrors;
|
|
|
|
|
|
|
|
// Connection aborted during handshake phase.
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check if we're encrypted or not.
|
|
|
|
if (result <= 0) {
|
|
|
|
switch (q_SSL_get_error(ssl, result)) {
|
|
|
|
case SSL_ERROR_WANT_READ:
|
|
|
|
case SSL_ERROR_WANT_WRITE:
|
|
|
|
// The handshake is not yet complete.
|
|
|
|
break;
|
|
|
|
default:
|
2018-06-27 09:38:53 +00:00
|
|
|
QString errorString = QSslSocketBackendPrivate::msgErrorsDuringHandshake();
|
2011-04-27 10:05:43 +00:00
|
|
|
#ifdef QSSLSOCKET_DEBUG
|
2015-07-10 14:10:44 +00:00
|
|
|
qCDebug(lcSsl) << "QSslSocketBackendPrivate::startHandshake: error!" << errorString;
|
2011-04-27 10:05:43 +00:00
|
|
|
#endif
|
2018-05-08 10:35:46 +00:00
|
|
|
{
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, errorString);
|
2019-10-30 10:49:07 +00:00
|
|
|
if (pendingFatalAlert) {
|
|
|
|
trySendFatalAlert();
|
|
|
|
pendingFatalAlert = false;
|
|
|
|
}
|
2018-05-08 10:35:46 +00:00
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
q->abort();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-02-03 14:32:53 +00:00
|
|
|
// store peer certificate chain
|
|
|
|
storePeerCertificates();
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
// Start translating errors.
|
|
|
|
QList<QSslError> errors;
|
|
|
|
|
2011-09-05 10:53:49 +00:00
|
|
|
// check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer)
|
2016-01-26 13:38:54 +00:00
|
|
|
for (const QSslCertificate &cert : qAsConst(configuration.peerCertificateChain)) {
|
2011-09-05 10:53:49 +00:00
|
|
|
if (QSslCertificatePrivate::isBlacklisted(cert)) {
|
|
|
|
QSslError error(QSslError::CertificateBlacklisted, cert);
|
|
|
|
errors << error;
|
|
|
|
emit q->peerVerifyError(error);
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
const bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer
|
|
|
|
|| (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer
|
|
|
|
&& mode == QSslSocket::SslClientMode);
|
|
|
|
|
|
|
|
#if QT_CONFIG(ocsp)
|
|
|
|
// For now it's always QSslSocket::SslClientMode - initSslContext() will bail out early,
|
|
|
|
// if it's enabled in QSslSocket::SslServerMode. This can change.
|
|
|
|
if (!configuration.peerCertificate.isNull() && configuration.ocspStaplingEnabled && doVerifyPeer) {
|
|
|
|
if (!checkOcspStatus()) {
|
|
|
|
if (ocspErrors.isEmpty()) {
|
|
|
|
{
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, ocspErrorDescription);
|
|
|
|
}
|
|
|
|
q->abort();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const QSslError &error : ocspErrors) {
|
|
|
|
errors << error;
|
|
|
|
emit q->peerVerifyError(error);
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // ocsp
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
// Check the peer certificate itself. First try the subject's common name
|
|
|
|
// (CN) as a wildcard, then try all alternate subject name DNS entries the
|
|
|
|
// same way.
|
|
|
|
if (!configuration.peerCertificate.isNull()) {
|
|
|
|
// but only if we're a client connecting to a server
|
|
|
|
// if we're the server, don't check CN
|
|
|
|
if (mode == QSslSocket::SslClientMode) {
|
|
|
|
QString peerName = (verificationPeerName.isEmpty () ? q->peerName() : verificationPeerName);
|
|
|
|
|
2011-06-18 14:53:53 +00:00
|
|
|
if (!isMatchingHostname(configuration.peerCertificate, peerName)) {
|
|
|
|
// No matches in common names or alternate names.
|
|
|
|
QSslError error(QSslError::HostNameMismatch, configuration.peerCertificate);
|
|
|
|
errors << error;
|
|
|
|
emit q->peerVerifyError(error);
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// No peer certificate presented. Report as error if the socket
|
|
|
|
// expected one.
|
|
|
|
if (doVerifyPeer) {
|
|
|
|
QSslError error(QSslError::NoPeerCertificate);
|
|
|
|
errors << error;
|
|
|
|
emit q->peerVerifyError(error);
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Translate errors from the error list into QSslErrors.
|
2016-01-17 13:16:43 +00:00
|
|
|
errors.reserve(errors.size() + errorList.size());
|
|
|
|
for (const auto &error : qAsConst(errorList))
|
|
|
|
errors << _q_OpenSSL_to_QSslError(error.code, configuration.peerCertificateChain.value(error.depth));
|
2011-04-27 10:05:43 +00:00
|
|
|
|
|
|
|
if (!errors.isEmpty()) {
|
|
|
|
sslErrors = errors;
|
2012-03-23 11:01:42 +00:00
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
2020-05-14 14:40:08 +00:00
|
|
|
const bool fetchEnabled = s_loadRootCertsOnDemand
|
|
|
|
&& allowRootCertOnDemandLoading;
|
|
|
|
// !fetchEnabled is a special case scenario, when we potentially have a missing
|
|
|
|
// intermediate certificate and a recoverable chain, but on demand cert loading
|
|
|
|
// was disabled by setCaCertificates call. For this scenario we check if "Authority
|
|
|
|
// Information Access" is present - wincrypt can deal with such certificates.
|
|
|
|
QSslCertificate certToFetch;
|
|
|
|
if (doVerifyPeer && !verifyErrorsHaveBeenIgnored())
|
|
|
|
certToFetch = findCertificateToFetch(sslErrors, !fetchEnabled);
|
|
|
|
|
2012-03-23 11:01:42 +00:00
|
|
|
//Skip this if not using system CAs, or if the SSL errors are configured in advance to be ignorable
|
2020-05-14 14:40:08 +00:00
|
|
|
if (!certToFetch.isNull()) {
|
|
|
|
fetchAuthorityInformation = !fetchEnabled;
|
|
|
|
//Windows desktop versions starting from vista ship with minimal set of roots and download on demand
|
|
|
|
//from the windows update server CA roots that are trusted by MS. It also can fetch a missing intermediate
|
|
|
|
//in case "Authority Information Access" extension is present.
|
|
|
|
//
|
2012-03-23 11:01:42 +00:00
|
|
|
//However, this is only transparent if using WinINET - we have to trigger it
|
|
|
|
//ourselves.
|
2020-05-14 14:40:08 +00:00
|
|
|
fetchCaRootForCert(certToFetch);
|
|
|
|
return false;
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
2012-03-23 11:01:42 +00:00
|
|
|
#endif
|
|
|
|
if (!checkSslErrors())
|
|
|
|
return false;
|
2016-06-09 08:36:50 +00:00
|
|
|
// A slot, attached to sslErrors signal can call
|
|
|
|
// abort/close/disconnetFromHost/etc; no need to
|
|
|
|
// continue handshake then.
|
|
|
|
if (q->state() != QAbstractSocket::ConnectedState)
|
|
|
|
return false;
|
2011-04-27 10:05:43 +00:00
|
|
|
} else {
|
|
|
|
sslErrors.clear();
|
|
|
|
}
|
|
|
|
|
2012-01-20 12:55:15 +00:00
|
|
|
continueHandshake();
|
2011-04-27 10:05:43 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-02-03 14:32:53 +00:00
|
|
|
void QSslSocketBackendPrivate::storePeerCertificates()
|
|
|
|
{
|
|
|
|
// Store the peer certificate and chain. For clients, the peer certificate
|
|
|
|
// chain includes the peer certificate; for servers, it doesn't. Both the
|
|
|
|
// peer certificate and the chain may be empty if the peer didn't present
|
|
|
|
// any certificate.
|
|
|
|
X509 *x509 = q_SSL_get_peer_certificate(ssl);
|
|
|
|
configuration.peerCertificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
|
|
|
|
q_X509_free(x509);
|
|
|
|
if (configuration.peerCertificateChain.isEmpty()) {
|
|
|
|
configuration.peerCertificateChain = STACKOFX509_to_QSslCertificates(q_SSL_get_peer_cert_chain(ssl));
|
|
|
|
if (!configuration.peerCertificate.isNull() && mode == QSslSocket::SslServerMode)
|
|
|
|
configuration.peerCertificateChain.prepend(configuration.peerCertificate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-27 13:11:08 +00:00
|
|
|
int QSslSocketBackendPrivate::handleNewSessionTicket(SSL *connection)
|
|
|
|
{
|
|
|
|
// If we return 1, this means we own the session, but we don't.
|
|
|
|
// 0 would tell OpenSSL to deref (but they still have it in the
|
|
|
|
// internal cache).
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
|
|
|
Q_ASSERT(connection);
|
|
|
|
|
|
|
|
if (q->sslConfiguration().testSslOption(QSsl::SslOptionDisableSessionPersistence)) {
|
|
|
|
// We silently ignore, do nothing, remove from cache.
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
SSL_SESSION *currentSession = q_SSL_get_session(connection);
|
|
|
|
if (!currentSession) {
|
|
|
|
qCWarning(lcSsl,
|
|
|
|
"New session ticket callback, the session is invalid (nullptr)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (q_SSL_version(connection) < 0x304) {
|
|
|
|
// We only rely on this mechanics with TLS >= 1.3
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef TLS1_3_VERSION
|
|
|
|
if (!q_SSL_SESSION_is_resumable(currentSession)) {
|
|
|
|
qCDebug(lcSsl, "New session ticket, but the session is non-resumable");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif // TLS1_3_VERSION
|
|
|
|
|
|
|
|
const int sessionSize = q_i2d_SSL_SESSION(currentSession, nullptr);
|
|
|
|
if (sessionSize <= 0) {
|
|
|
|
qCWarning(lcSsl, "could not store persistent version of SSL session");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have somewhat perverse naming, it's not a ticket, it's a session.
|
|
|
|
QByteArray sessionTicket(sessionSize, 0);
|
|
|
|
auto data = reinterpret_cast<unsigned char *>(sessionTicket.data());
|
|
|
|
if (!q_i2d_SSL_SESSION(currentSession, &data)) {
|
|
|
|
qCWarning(lcSsl, "could not store persistent version of SSL session");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration.sslSession = sessionTicket;
|
|
|
|
configuration.sslSessionTicketLifeTimeHint = int(q_SSL_SESSION_get_ticket_lifetime_hint(currentSession));
|
|
|
|
|
|
|
|
emit q->newSessionTicketReceived();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-23 11:01:42 +00:00
|
|
|
bool QSslSocketBackendPrivate::checkSslErrors()
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
if (sslErrors.isEmpty())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
emit q->sslErrors(sslErrors);
|
|
|
|
|
|
|
|
bool doVerifyPeer = configuration.peerVerifyMode == QSslSocket::VerifyPeer
|
|
|
|
|| (configuration.peerVerifyMode == QSslSocket::AutoVerifyPeer
|
|
|
|
&& mode == QSslSocket::SslClientMode);
|
|
|
|
bool doEmitSslError = !verifyErrorsHaveBeenIgnored();
|
|
|
|
// check whether we need to emit an SSL handshake error
|
|
|
|
if (doVerifyPeer && doEmitSslError) {
|
2012-04-30 18:14:06 +00:00
|
|
|
if (q->pauseMode() & QAbstractSocket::PauseOnSslErrors) {
|
2012-03-23 11:01:42 +00:00
|
|
|
pauseSocketNotifiers(q);
|
|
|
|
paused = true;
|
|
|
|
} else {
|
2016-04-13 15:49:59 +00:00
|
|
|
setErrorAndEmit(QAbstractSocket::SslHandshakeFailedError, sslErrors.constFirst().errorString());
|
2012-03-23 11:01:42 +00:00
|
|
|
plainSocket->disconnectFromHost();
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-10-15 13:13:47 +00:00
|
|
|
unsigned int QSslSocketBackendPrivate::tlsPskClientCallback(const char *hint,
|
|
|
|
char *identity, unsigned int max_identity_len,
|
|
|
|
unsigned char *psk, unsigned int max_psk_len)
|
|
|
|
{
|
|
|
|
QSslPreSharedKeyAuthenticator authenticator;
|
|
|
|
|
|
|
|
// Fill in some read-only fields (for the user)
|
|
|
|
if (hint)
|
|
|
|
authenticator.d->identityHint = QByteArray::fromRawData(hint, int(::strlen(hint))); // it's NUL terminated, but do not include the NUL
|
|
|
|
|
|
|
|
authenticator.d->maximumIdentityLength = int(max_identity_len) - 1; // needs to be NUL terminated
|
|
|
|
authenticator.d->maximumPreSharedKeyLength = int(max_psk_len);
|
|
|
|
|
|
|
|
// Let the client provide the remaining bits...
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
emit q->preSharedKeyAuthenticationRequired(&authenticator);
|
|
|
|
|
|
|
|
// No PSK set? Return now to make the handshake fail
|
|
|
|
if (authenticator.preSharedKey().isEmpty())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Copy data back into OpenSSL
|
|
|
|
const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength());
|
|
|
|
::memcpy(identity, authenticator.identity().constData(), identityLength);
|
|
|
|
identity[identityLength] = 0;
|
|
|
|
|
|
|
|
const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength());
|
|
|
|
::memcpy(psk, authenticator.preSharedKey().constData(), pskLength);
|
2016-03-23 11:27:11 +00:00
|
|
|
return pskLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int QSslSocketBackendPrivate::tlsPskServerCallback(const char *identity,
|
|
|
|
unsigned char *psk, unsigned int max_psk_len)
|
|
|
|
{
|
|
|
|
QSslPreSharedKeyAuthenticator authenticator;
|
|
|
|
|
|
|
|
// Fill in some read-only fields (for the user)
|
|
|
|
authenticator.d->identityHint = configuration.preSharedKeyIdentityHint;
|
|
|
|
authenticator.d->identity = identity;
|
|
|
|
authenticator.d->maximumIdentityLength = 0; // user cannot set an identity
|
|
|
|
authenticator.d->maximumPreSharedKeyLength = int(max_psk_len);
|
|
|
|
|
|
|
|
// Let the client provide the remaining bits...
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
emit q->preSharedKeyAuthenticationRequired(&authenticator);
|
|
|
|
|
|
|
|
// No PSK set? Return now to make the handshake fail
|
|
|
|
if (authenticator.preSharedKey().isEmpty())
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Copy data back into OpenSSL
|
|
|
|
const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength());
|
|
|
|
::memcpy(psk, authenticator.preSharedKey().constData(), pskLength);
|
2014-10-15 13:13:47 +00:00
|
|
|
return pskLength;
|
|
|
|
}
|
|
|
|
|
2012-03-23 11:01:42 +00:00
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::fetchCaRootForCert(const QSslCertificate &cert)
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
//The root certificate is downloaded from windows update, which blocks for 15 seconds in the worst case
|
|
|
|
//so the request is done in a worker thread.
|
2020-05-14 14:40:08 +00:00
|
|
|
QList<QSslCertificate> customRoots;
|
|
|
|
if (fetchAuthorityInformation)
|
|
|
|
customRoots = configuration.caCertificates;
|
|
|
|
|
|
|
|
QWindowsCaRootFetcher *fetcher = new QWindowsCaRootFetcher(cert, mode, customRoots, q->peerVerifyName());
|
2012-03-23 11:01:42 +00:00
|
|
|
QObject::connect(fetcher, SIGNAL(finished(QSslCertificate,QSslCertificate)), q, SLOT(_q_caRootLoaded(QSslCertificate,QSslCertificate)), Qt::QueuedConnection);
|
|
|
|
QMetaObject::invokeMethod(fetcher, "start", Qt::QueuedConnection);
|
|
|
|
pauseSocketNotifiers(q);
|
|
|
|
paused = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
//This is the callback from QWindowsCaRootFetcher, trustedRoot will be invalid (default constructed) if it failed.
|
|
|
|
void QSslSocketBackendPrivate::_q_caRootLoaded(QSslCertificate cert, QSslCertificate trustedRoot)
|
|
|
|
{
|
2020-05-14 14:40:08 +00:00
|
|
|
if (fetchAuthorityInformation) {
|
|
|
|
if (!configuration.caCertificates.contains(trustedRoot))
|
|
|
|
trustedRoot = QSslCertificate{};
|
|
|
|
fetchAuthorityInformation = false;
|
|
|
|
}
|
|
|
|
|
2012-05-21 14:19:50 +00:00
|
|
|
if (!trustedRoot.isNull() && !trustedRoot.isBlacklisted()) {
|
2012-03-23 11:01:42 +00:00
|
|
|
if (s_loadRootCertsOnDemand) {
|
|
|
|
//Add the new root cert to default cert list for use by future sockets
|
2020-06-15 14:23:28 +00:00
|
|
|
auto defaultConfig = QSslConfiguration::defaultConfiguration();
|
|
|
|
defaultConfig.addCaCertificate(trustedRoot);
|
|
|
|
QSslConfiguration::setDefaultConfiguration(defaultConfig);
|
2012-03-23 11:01:42 +00:00
|
|
|
}
|
|
|
|
//Add the new root cert to this socket for future connections
|
2020-05-14 10:58:15 +00:00
|
|
|
if (!configuration.caCertificates.contains(trustedRoot))
|
|
|
|
configuration.caCertificates += trustedRoot;
|
2012-03-23 11:01:42 +00:00
|
|
|
//Remove the broken chain ssl errors (as chain is verified by windows)
|
|
|
|
for (int i=sslErrors.count() - 1; i >= 0; --i) {
|
|
|
|
if (sslErrors.at(i).certificate() == cert) {
|
|
|
|
switch (sslErrors.at(i).error()) {
|
|
|
|
case QSslError::UnableToGetLocalIssuerCertificate:
|
|
|
|
case QSslError::CertificateUntrusted:
|
|
|
|
case QSslError::UnableToVerifyFirstCertificate:
|
2012-04-27 19:05:05 +00:00
|
|
|
case QSslError::SelfSignedCertificateInChain:
|
2012-03-23 11:01:42 +00:00
|
|
|
// error can be ignored if OS says the chain is trusted
|
|
|
|
sslErrors.removeAt(i);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// error cannot be ignored
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-05-14 14:40:08 +00:00
|
|
|
|
2012-03-23 11:01:42 +00:00
|
|
|
// Continue with remaining errors
|
|
|
|
if (plainSocket)
|
|
|
|
plainSocket->resume();
|
|
|
|
paused = false;
|
2016-03-01 20:10:04 +00:00
|
|
|
if (checkSslErrors() && ssl) {
|
|
|
|
bool willClose = (autoStartHandshake && pendingClose);
|
2012-03-23 11:01:42 +00:00
|
|
|
continueHandshake();
|
2016-03-01 20:10:04 +00:00
|
|
|
if (!willClose)
|
|
|
|
transmit();
|
|
|
|
}
|
2012-03-23 11:01:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2018-10-25 08:44:16 +00:00
|
|
|
#if QT_CONFIG(ocsp)
|
|
|
|
|
|
|
|
bool QSslSocketBackendPrivate::checkOcspStatus()
|
|
|
|
{
|
|
|
|
Q_ASSERT(ssl);
|
|
|
|
Q_ASSERT(mode == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode
|
|
|
|
Q_ASSERT(configuration.peerVerifyMode != QSslSocket::VerifyNone);
|
|
|
|
|
2019-01-25 14:11:34 +00:00
|
|
|
ocspResponses.clear();
|
2018-10-25 08:44:16 +00:00
|
|
|
ocspErrorDescription.clear();
|
|
|
|
ocspErrors.clear();
|
|
|
|
|
|
|
|
const unsigned char *responseData = nullptr;
|
|
|
|
const long responseLength = q_SSL_get_tlsext_status_ocsp_resp(ssl, &responseData);
|
|
|
|
if (responseLength <= 0 || !responseData) {
|
|
|
|
ocspErrors.push_back(QSslError::OcspNoResponseFound);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCSP_RESPONSE *response = q_d2i_OCSP_RESPONSE(nullptr, &responseData, responseLength);
|
|
|
|
if (!response) {
|
|
|
|
// Treat this as a fatal SslHandshakeError.
|
|
|
|
ocspErrorDescription = QSslSocket::tr("Failed to decode OCSP response");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const QSharedPointer<OCSP_RESPONSE> responseGuard(response, q_OCSP_RESPONSE_free);
|
|
|
|
|
|
|
|
const int ocspStatus = q_OCSP_response_status(response);
|
|
|
|
if (ocspStatus != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
|
|
|
|
// It's not a definitive response, it's an error message (not signed by the responder).
|
|
|
|
ocspErrors.push_back(qt_OCSP_response_status_to_QSslError(ocspStatus));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCSP_BASICRESP *basicResponse = q_OCSP_response_get1_basic(response);
|
|
|
|
if (!basicResponse) {
|
|
|
|
// SslHandshakeError.
|
|
|
|
ocspErrorDescription = QSslSocket::tr("Failed to extract basic OCSP response");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const QSharedPointer<OCSP_BASICRESP> basicResponseGuard(basicResponse, q_OCSP_BASICRESP_free);
|
|
|
|
|
|
|
|
SSL_CTX *ctx = q_SSL_get_SSL_CTX(ssl); // Does not increment refcount.
|
|
|
|
Q_ASSERT(ctx);
|
|
|
|
X509_STORE *store = q_SSL_CTX_get_cert_store(ctx); // Does not increment refcount.
|
|
|
|
if (!store) {
|
|
|
|
// SslHandshakeError.
|
|
|
|
ocspErrorDescription = QSslSocket::tr("No certificate verification store, cannot verify OCSP response");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
STACK_OF(X509) *peerChain = q_SSL_get_peer_cert_chain(ssl); // Does not increment refcount.
|
|
|
|
X509 *peerX509 = q_SSL_get_peer_certificate(ssl);
|
|
|
|
Q_ASSERT(peerChain || peerX509);
|
|
|
|
const QSharedPointer<X509> peerX509Guard(peerX509, q_X509_free);
|
|
|
|
// OCSP_basic_verify with 0 as verificationFlags:
|
|
|
|
//
|
|
|
|
// 0) Tries to find the OCSP responder's certificate in either peerChain
|
|
|
|
// or basicResponse->certs. If not found, verification fails.
|
|
|
|
// 1) It checks the signature using the responder's public key.
|
|
|
|
// 2) Then it tries to validate the responder's cert (building a chain
|
|
|
|
// etc.)
|
|
|
|
// 3) It checks CertID in response.
|
|
|
|
// 4) Ensures the responder is authorized to sign the status respond.
|
|
|
|
//
|
2019-06-25 09:17:32 +00:00
|
|
|
// Note, OpenSSL prior to 1.0.2b would only use bs->certs to
|
2018-10-25 08:44:16 +00:00
|
|
|
// verify the responder's chain (see their commit 4ba9a4265bd).
|
|
|
|
// Working this around - is too much fuss for ancient versions we
|
|
|
|
// are dropping quite soon anyway.
|
2019-06-25 09:17:32 +00:00
|
|
|
const unsigned long verificationFlags = 0;
|
|
|
|
const int success = q_OCSP_basic_verify(basicResponse, peerChain, store, verificationFlags);
|
|
|
|
if (success <= 0)
|
|
|
|
ocspErrors.push_back(QSslError::OcspResponseCannotBeTrusted);
|
2018-10-25 08:44:16 +00:00
|
|
|
|
|
|
|
if (q_OCSP_resp_count(basicResponse) != 1) {
|
|
|
|
ocspErrors.push_back(QSslError::OcspMalformedResponse);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCSP_SINGLERESP *singleResponse = q_OCSP_resp_get0(basicResponse, 0);
|
|
|
|
if (!singleResponse) {
|
|
|
|
ocspErrors.clear();
|
|
|
|
// A fatal problem -> SslHandshakeError.
|
|
|
|
ocspErrorDescription = QSslSocket::tr("Failed to decode a SingleResponse from OCSP status response");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Let's make sure the response is for the correct certificate - we
|
|
|
|
// can re-create this CertID using our peer's certificate and its
|
|
|
|
// issuer's public key.
|
2019-01-25 14:11:34 +00:00
|
|
|
ocspResponses.push_back(QOcspResponse());
|
|
|
|
QOcspResponsePrivate *dResponse = ocspResponses.back().d.data();
|
|
|
|
dResponse->subjectCert = configuration.peerCertificate;
|
2018-10-25 08:44:16 +00:00
|
|
|
bool matchFound = false;
|
|
|
|
if (configuration.peerCertificate.isSelfSigned()) {
|
2019-01-16 13:43:09 +00:00
|
|
|
dResponse->signerCert = configuration.peerCertificate;
|
2018-10-25 08:44:16 +00:00
|
|
|
matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, peerX509);
|
|
|
|
} else {
|
|
|
|
const STACK_OF(X509) *certs = q_SSL_get_peer_cert_chain(ssl);
|
|
|
|
if (!certs) // Oh, what a cataclysm! Last try:
|
|
|
|
certs = q_OCSP_resp_get0_certs(basicResponse);
|
|
|
|
if (certs) {
|
|
|
|
// It could be the first certificate in 'certs' is our peer's
|
|
|
|
// certificate. Since it was not captured by the 'self-signed' branch
|
|
|
|
// above, the CertID will not match and we'll just iterate on to the
|
|
|
|
// next certificate. So we start from 0, not 1.
|
|
|
|
for (int i = 0, e = q_sk_X509_num(certs); i < e; ++i) {
|
|
|
|
X509 *issuer = q_sk_X509_value(certs, i);
|
|
|
|
matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer);
|
|
|
|
if (matchFound) {
|
2019-01-16 13:43:09 +00:00
|
|
|
if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) {
|
|
|
|
dResponse->signerCert = QSslCertificatePrivate::QSslCertificate_from_X509(issuer);
|
2018-10-25 08:44:16 +00:00
|
|
|
break;
|
2019-01-16 13:43:09 +00:00
|
|
|
}
|
2018-10-25 08:44:16 +00:00
|
|
|
matchFound = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-16 13:43:09 +00:00
|
|
|
if (!matchFound) {
|
|
|
|
dResponse->signerCert.clear();
|
2018-10-25 08:44:16 +00:00
|
|
|
ocspErrors.push_back({QSslError::OcspResponseCertIdUnknown, configuration.peerCertificate});
|
2019-01-16 13:43:09 +00:00
|
|
|
}
|
2018-10-25 08:44:16 +00:00
|
|
|
|
|
|
|
// Check if the response is valid time-wise:
|
|
|
|
ASN1_GENERALIZEDTIME *revTime = nullptr;
|
|
|
|
ASN1_GENERALIZEDTIME *thisUpdate = nullptr;
|
|
|
|
ASN1_GENERALIZEDTIME *nextUpdate = nullptr;
|
|
|
|
int reason;
|
|
|
|
const int certStatus = q_OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate);
|
|
|
|
if (!thisUpdate) {
|
|
|
|
// This is unexpected, treat as SslHandshakeError, OCSP_check_validity assumes this pointer
|
|
|
|
// to be != nullptr.
|
|
|
|
ocspErrors.clear();
|
2019-01-25 14:11:34 +00:00
|
|
|
ocspResponses.clear();
|
2018-10-25 08:44:16 +00:00
|
|
|
ocspErrorDescription = QSslSocket::tr("Failed to extract 'this update time' from the SingleResponse");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// OCSP_check_validity(this, next, nsec, maxsec) does this check:
|
|
|
|
// this <= now <= next. They allow some freedom to account
|
|
|
|
// for delays/time inaccuracy.
|
|
|
|
// this > now + nsec ? -> NOT_YET_VALID
|
|
|
|
// if maxsec >= 0:
|
|
|
|
// now - maxsec > this ? -> TOO_OLD
|
|
|
|
// now - nsec > next ? -> EXPIRED
|
|
|
|
// next < this ? -> NEXT_BEFORE_THIS
|
|
|
|
// OK.
|
|
|
|
if (!q_OCSP_check_validity(thisUpdate, nextUpdate, 60, -1))
|
|
|
|
ocspErrors.push_back({QSslError::OcspResponseExpired, configuration.peerCertificate});
|
|
|
|
|
|
|
|
// And finally, the status:
|
|
|
|
switch (certStatus) {
|
|
|
|
case V_OCSP_CERTSTATUS_GOOD:
|
|
|
|
// This certificate was not found among the revoked ones.
|
2019-02-11 15:07:04 +00:00
|
|
|
dResponse->certificateStatus = QOcspCertificateStatus::Good;
|
2018-10-25 08:44:16 +00:00
|
|
|
break;
|
|
|
|
case V_OCSP_CERTSTATUS_REVOKED:
|
2019-02-11 15:07:04 +00:00
|
|
|
dResponse->certificateStatus = QOcspCertificateStatus::Revoked;
|
2019-01-16 13:43:09 +00:00
|
|
|
dResponse->revocationReason = qt_OCSP_revocation_reason(reason);
|
2018-10-25 08:44:16 +00:00
|
|
|
ocspErrors.push_back({QSslError::CertificateRevoked, configuration.peerCertificate});
|
|
|
|
break;
|
|
|
|
case V_OCSP_CERTSTATUS_UNKNOWN:
|
2019-02-11 15:07:04 +00:00
|
|
|
dResponse->certificateStatus = QOcspCertificateStatus::Unknown;
|
2018-10-25 08:44:16 +00:00
|
|
|
ocspErrors.push_back({QSslError::OcspStatusUnknown, configuration.peerCertificate});
|
|
|
|
}
|
|
|
|
|
|
|
|
return !ocspErrors.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // ocsp
|
|
|
|
|
2019-10-30 10:49:07 +00:00
|
|
|
void QSslSocketBackendPrivate::alertMessageSent(int value)
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
|
|
|
const auto level = tlsAlertLevel(value);
|
2020-06-25 08:44:57 +00:00
|
|
|
if (level == QSsl::AlertLevel::Fatal && !connectionEncrypted) {
|
2019-10-30 10:49:07 +00:00
|
|
|
// Note, this logic is handshake-time only:
|
|
|
|
pendingFatalAlert = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit q->alertSent(level, tlsAlertType(value), tlsAlertDescription(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::alertMessageReceived(int value)
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
|
|
|
emit q->alertReceived(tlsAlertLevel(value), tlsAlertType(value), tlsAlertDescription(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
int QSslSocketBackendPrivate::emitErrorFromCallback(X509_STORE_CTX *ctx)
|
|
|
|
{
|
|
|
|
// Returns 0 to abort verification, 1 to continue despite error (as
|
|
|
|
// OpenSSL expects from the verification callback).
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
|
|
|
|
Q_ASSERT(ctx);
|
|
|
|
|
|
|
|
using ScopedBool = QScopedValueRollback<bool>;
|
|
|
|
// While we are not setting, we are emitting and in general -
|
|
|
|
// we want to prevent accidental recursive startHandshake()
|
|
|
|
// calls:
|
|
|
|
const ScopedBool bg(inSetAndEmitError, true);
|
|
|
|
|
|
|
|
X509 *x509 = q_X509_STORE_CTX_get_current_cert(ctx);
|
|
|
|
if (!x509) {
|
|
|
|
qCWarning(lcSsl, "Could not obtain the certificate (that failed to verify)");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
const QSslCertificate certificate = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
|
|
|
|
|
|
|
|
const auto errorAndDepth = QSslErrorEntry::fromStoreContext(ctx);
|
|
|
|
const QSslError tlsError = _q_OpenSSL_to_QSslError(errorAndDepth.code, certificate);
|
|
|
|
|
|
|
|
errorsReportedFromCallback = true;
|
|
|
|
handshakeInterrupted = true;
|
|
|
|
emit q->handshakeInterruptedOnError(tlsError);
|
|
|
|
|
|
|
|
// Conveniently so, we also can access 'lastErrors' external data set
|
|
|
|
// in startHandshake, we store it for the case an application later
|
|
|
|
// wants to check errors (ignored or not):
|
|
|
|
const auto offset = QSslSocketBackendPrivate::s_indexForSSLExtraData
|
|
|
|
+ QSslSocketBackendPrivate::errorOffsetInExData;
|
2020-06-22 10:25:41 +00:00
|
|
|
if (auto errorList = static_cast<QList<QSslErrorEntry> *>(q_SSL_get_ex_data(ssl, offset)))
|
2019-10-30 10:49:07 +00:00
|
|
|
errorList->append(errorAndDepth);
|
|
|
|
|
|
|
|
// An application is expected to ignore this error (by calling ignoreSslErrors)
|
|
|
|
// in its directly connected slot:
|
|
|
|
return !handshakeInterrupted;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::trySendFatalAlert()
|
|
|
|
{
|
|
|
|
Q_ASSERT(pendingFatalAlert);
|
|
|
|
|
|
|
|
pendingFatalAlert = false;
|
|
|
|
QVarLengthArray<char, 4096> data;
|
|
|
|
int pendingBytes = 0;
|
|
|
|
while (plainSocket->isValid() && (pendingBytes = q_BIO_pending(writeBio)) > 0
|
|
|
|
&& plainSocket->openMode() != QIODevice::NotOpen) {
|
|
|
|
// Read encrypted data from the write BIO into a buffer.
|
|
|
|
data.resize(pendingBytes);
|
|
|
|
const int bioReadBytes = q_BIO_read(writeBio, data.data(), pendingBytes);
|
|
|
|
|
|
|
|
// Write encrypted data from the buffer to the socket.
|
|
|
|
qint64 actualWritten = plainSocket->write(data.constData(), bioReadBytes);
|
|
|
|
if (actualWritten < 0)
|
|
|
|
return;
|
|
|
|
plainSocket->flush();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
void QSslSocketBackendPrivate::disconnectFromHost()
|
|
|
|
{
|
|
|
|
if (ssl) {
|
2020-04-13 18:31:34 +00:00
|
|
|
if (!shutdown && !q_SSL_in_init(ssl) && !systemOrSslErrorDetected) {
|
|
|
|
if (q_SSL_shutdown(ssl) != 1) {
|
|
|
|
// Some error may be queued, clear it.
|
|
|
|
const auto errors = getErrorsFromOpenSsl();
|
|
|
|
Q_UNUSED(errors);
|
|
|
|
}
|
2013-04-04 09:30:43 +00:00
|
|
|
shutdown = true;
|
|
|
|
transmit();
|
|
|
|
}
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
plainSocket->disconnectFromHost();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::disconnected()
|
|
|
|
{
|
2012-01-30 15:52:27 +00:00
|
|
|
if (plainSocket->bytesAvailable() <= 0)
|
|
|
|
destroySslContext();
|
2015-06-19 13:35:34 +00:00
|
|
|
else {
|
|
|
|
// Move all bytes into the plain buffer
|
|
|
|
qint64 tmpReadBufferMaxSize = readBufferMaxSize;
|
|
|
|
readBufferMaxSize = 0; // reset temporarily so the plain socket buffer is completely drained
|
|
|
|
transmit();
|
|
|
|
readBufferMaxSize = tmpReadBufferMaxSize;
|
|
|
|
}
|
2012-01-30 15:52:27 +00:00
|
|
|
//if there is still buffered data in the plain socket, don't destroy the ssl context yet.
|
|
|
|
//it will be destroyed when the socket is deleted.
|
2011-04-27 10:05:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QSslCipher QSslSocketBackendPrivate::sessionCipher() const
|
|
|
|
{
|
2012-12-21 13:02:38 +00:00
|
|
|
if (!ssl)
|
2011-04-27 10:05:43 +00:00
|
|
|
return QSslCipher();
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
|
|
|
|
const SSL_CIPHER *sessionCipher = q_SSL_get_current_cipher(ssl);
|
2011-04-27 10:05:43 +00:00
|
|
|
return sessionCipher ? QSslCipher_from_SSL_CIPHER(sessionCipher) : QSslCipher();
|
|
|
|
}
|
|
|
|
|
2014-03-10 13:00:08 +00:00
|
|
|
QSsl::SslProtocol QSslSocketBackendPrivate::sessionProtocol() const
|
|
|
|
{
|
|
|
|
if (!ssl)
|
|
|
|
return QSsl::UnknownProtocol;
|
|
|
|
int ver = q_SSL_version(ssl);
|
|
|
|
|
|
|
|
switch (ver) {
|
|
|
|
case 0x301:
|
|
|
|
return QSsl::TlsV1_0;
|
|
|
|
case 0x302:
|
|
|
|
return QSsl::TlsV1_1;
|
|
|
|
case 0x303:
|
|
|
|
return QSsl::TlsV1_2;
|
2018-10-29 13:26:15 +00:00
|
|
|
case 0x304:
|
|
|
|
return QSsl::TlsV1_3;
|
2014-03-10 13:00:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return QSsl::UnknownProtocol;
|
|
|
|
}
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
|
|
|
|
void QSslSocketBackendPrivate::continueHandshake()
|
|
|
|
{
|
|
|
|
Q_Q(QSslSocket);
|
|
|
|
// if we have a max read buffer size, reset the plain socket's to match
|
|
|
|
if (readBufferMaxSize)
|
|
|
|
plainSocket->setReadBufferSize(readBufferMaxSize);
|
|
|
|
|
|
|
|
if (q_SSL_session_reused(ssl))
|
|
|
|
configuration.peerSessionShared = true;
|
|
|
|
|
|
|
|
#ifdef QT_DECRYPT_SSL_TRAFFIC
|
|
|
|
if (q_SSL_get_session(ssl)) {
|
|
|
|
size_t master_key_len = q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl), 0, 0);
|
|
|
|
size_t client_random_len = q_SSL_get_client_random(ssl, 0, 0);
|
|
|
|
QByteArray masterKey(int(master_key_len), 0); // Will not overflow
|
|
|
|
QByteArray clientRandom(int(client_random_len), 0); // Will not overflow
|
|
|
|
|
|
|
|
q_SSL_SESSION_get_master_key(q_SSL_get_session(ssl),
|
|
|
|
reinterpret_cast<unsigned char*>(masterKey.data()),
|
|
|
|
masterKey.size());
|
|
|
|
q_SSL_get_client_random(ssl, reinterpret_cast<unsigned char *>(clientRandom.data()),
|
|
|
|
clientRandom.size());
|
|
|
|
|
|
|
|
QByteArray debugLineClientRandom("CLIENT_RANDOM ");
|
|
|
|
debugLineClientRandom.append(clientRandom.toHex().toUpper());
|
|
|
|
debugLineClientRandom.append(" ");
|
|
|
|
debugLineClientRandom.append(masterKey.toHex().toUpper());
|
|
|
|
debugLineClientRandom.append("\n");
|
|
|
|
|
|
|
|
QString sslKeyFile = QDir::tempPath() + QLatin1String("/qt-ssl-keys");
|
|
|
|
QFile file(sslKeyFile);
|
|
|
|
if (!file.open(QIODevice::Append))
|
|
|
|
qCWarning(lcSsl) << "could not open file" << sslKeyFile << "for appending";
|
|
|
|
if (!file.write(debugLineClientRandom))
|
|
|
|
qCWarning(lcSsl) << "could not write to file" << sslKeyFile;
|
|
|
|
file.close();
|
|
|
|
} else {
|
|
|
|
qCWarning(lcSsl, "could not decrypt SSL traffic");
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Cache this SSL session inside the QSslContext
|
|
|
|
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionSharing)) {
|
|
|
|
if (!sslContextPointer->cacheSession(ssl)) {
|
|
|
|
sslContextPointer.clear(); // we could not cache the session
|
|
|
|
} else {
|
|
|
|
// Cache the session for permanent usage as well
|
|
|
|
if (!(configuration.sslOptions & QSsl::SslOptionDisableSessionPersistence)) {
|
|
|
|
if (!sslContextPointer->sessionASN1().isEmpty())
|
|
|
|
configuration.sslSession = sslContextPointer->sessionASN1();
|
|
|
|
configuration.sslSessionTicketLifeTimeHint = sslContextPointer->sessionTicketLifeTimeHint();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if !defined(OPENSSL_NO_NEXTPROTONEG)
|
|
|
|
|
|
|
|
configuration.nextProtocolNegotiationStatus = sslContextPointer->npnContext().status;
|
|
|
|
if (sslContextPointer->npnContext().status == QSslConfiguration::NextProtocolNegotiationUnsupported) {
|
|
|
|
// we could not agree -> be conservative and use HTTP/1.1
|
|
|
|
configuration.nextNegotiatedProtocol = QByteArrayLiteral("http/1.1");
|
|
|
|
} else {
|
|
|
|
const unsigned char *proto = nullptr;
|
|
|
|
unsigned int proto_len = 0;
|
|
|
|
|
|
|
|
q_SSL_get0_alpn_selected(ssl, &proto, &proto_len);
|
|
|
|
if (proto_len && mode == QSslSocket::SslClientMode) {
|
|
|
|
// Client does not have a callback that sets it ...
|
|
|
|
configuration.nextProtocolNegotiationStatus = QSslConfiguration::NextProtocolNegotiationNegotiated;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!proto_len) { // Test if NPN was more lucky ...
|
|
|
|
q_SSL_get0_next_proto_negotiated(ssl, &proto, &proto_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (proto_len)
|
|
|
|
configuration.nextNegotiatedProtocol = QByteArray(reinterpret_cast<const char *>(proto), proto_len);
|
|
|
|
else
|
|
|
|
configuration.nextNegotiatedProtocol.clear();
|
|
|
|
}
|
|
|
|
#endif // !defined(OPENSSL_NO_NEXTPROTONEG)
|
|
|
|
|
|
|
|
if (mode == QSslSocket::SslClientMode) {
|
|
|
|
EVP_PKEY *key;
|
|
|
|
if (q_SSL_get_server_tmp_key(ssl, &key))
|
|
|
|
configuration.ephemeralServerKey = QSslKey(key, QSsl::PublicKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
connectionEncrypted = true;
|
|
|
|
emit q->encrypted();
|
|
|
|
if (autoStartHandshake && pendingClose) {
|
|
|
|
pendingClose = false;
|
|
|
|
q->disconnectFromHost();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool QSslSocketPrivate::ensureLibraryLoaded()
|
|
|
|
{
|
|
|
|
if (!q_resolveOpenSslSymbols())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const QMutexLocker locker(qt_opensslInitMutex);
|
|
|
|
|
|
|
|
if (!s_libraryLoaded) {
|
|
|
|
// Initialize OpenSSL.
|
|
|
|
if (q_OPENSSL_init_ssl(0, nullptr) != 1)
|
|
|
|
return false;
|
2020-03-19 20:18:19 +00:00
|
|
|
|
|
|
|
if (q_OpenSSL_version_num() < 0x10101000L) {
|
|
|
|
qCWarning(lcSsl, "QSslSocket: OpenSSL >= 1.1.1 is required; %s was found instead", q_OpenSSL_version(OPENSSL_VERSION));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-09-27 11:04:54 +00:00
|
|
|
q_SSL_load_error_strings();
|
|
|
|
q_OpenSSL_add_all_algorithms();
|
|
|
|
|
|
|
|
QSslSocketBackendPrivate::s_indexForSSLExtraData
|
|
|
|
= q_CRYPTO_get_ex_new_index(CRYPTO_EX_INDEX_SSL, 0L, nullptr, nullptr,
|
|
|
|
nullptr, nullptr);
|
|
|
|
|
|
|
|
// Initialize OpenSSL's random seed.
|
|
|
|
if (!q_RAND_status()) {
|
|
|
|
qWarning("Random number generator not seeded, disabling SSL support");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
s_libraryLoaded = true;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QSslSocketPrivate::ensureCiphersAndCertsLoaded()
|
|
|
|
{
|
|
|
|
const QMutexLocker locker(qt_opensslInitMutex);
|
|
|
|
|
|
|
|
if (s_loadedCiphersAndCerts)
|
|
|
|
return;
|
|
|
|
s_loadedCiphersAndCerts = true;
|
|
|
|
|
|
|
|
resetDefaultCiphers();
|
|
|
|
resetDefaultEllipticCurves();
|
|
|
|
|
|
|
|
#if QT_CONFIG(library)
|
|
|
|
//load symbols needed to receive certificates from system store
|
|
|
|
#if defined(Q_OS_QNX)
|
|
|
|
s_loadRootCertsOnDemand = true;
|
|
|
|
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
|
|
|
|
// check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there)
|
|
|
|
QList<QByteArray> dirs = unixRootCertDirectories();
|
|
|
|
QStringList symLinkFilter;
|
|
|
|
symLinkFilter << QLatin1String("[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]");
|
|
|
|
for (int a = 0; a < dirs.count(); ++a) {
|
|
|
|
QDirIterator iterator(QLatin1String(dirs.at(a)), symLinkFilter, QDir::Files);
|
|
|
|
if (iterator.hasNext()) {
|
|
|
|
s_loadRootCertsOnDemand = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#endif // QT_CONFIG(library)
|
|
|
|
// if on-demand loading was not enabled, load the certs now
|
|
|
|
if (!s_loadRootCertsOnDemand)
|
|
|
|
setDefaultCaCertificates(systemCaCertificates());
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
//Enabled for fetching additional root certs from windows update on windows.
|
|
|
|
//This flag is set false by setDefaultCaCertificates() indicating the app uses
|
|
|
|
//its own cert bundle rather than the system one.
|
|
|
|
//Same logic that disables the unix on demand cert loading.
|
|
|
|
//Unlike unix, we do preload the certificates from the cert store.
|
|
|
|
s_loadRootCertsOnDemand = true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
QList<QSslCertificate> QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(STACK_OF(X509) *x509)
|
|
|
|
{
|
|
|
|
ensureInitialized();
|
|
|
|
QList<QSslCertificate> certificates;
|
|
|
|
for (int i = 0; i < q_sk_X509_num(x509); ++i) {
|
|
|
|
if (X509 *entry = q_sk_X509_value(x509, i))
|
|
|
|
certificates << QSslCertificatePrivate::QSslCertificate_from_X509(entry);
|
|
|
|
}
|
|
|
|
return certificates;
|
|
|
|
}
|
|
|
|
|
2020-05-14 14:40:08 +00:00
|
|
|
QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &certificateChain,
|
|
|
|
const QString &hostName)
|
2011-06-18 14:53:53 +00:00
|
|
|
{
|
2020-05-14 14:40:08 +00:00
|
|
|
if (s_loadRootCertsOnDemand)
|
|
|
|
setDefaultCaCertificates(defaultCaCertificates() + systemCaCertificates());
|
|
|
|
|
|
|
|
return verify(QSslConfiguration::defaultConfiguration().caCertificates(), certificateChain, hostName);
|
|
|
|
}
|
2011-06-18 14:53:53 +00:00
|
|
|
|
2020-05-14 14:40:08 +00:00
|
|
|
QList<QSslError> QSslSocketBackendPrivate::verify(const QList<QSslCertificate> &caCertificates,
|
|
|
|
const QList<QSslCertificate> &certificateChain,
|
|
|
|
const QString &hostName)
|
|
|
|
{
|
|
|
|
if (certificateChain.count() <= 0)
|
|
|
|
return {QSslError(QSslError::UnspecifiedError)};
|
|
|
|
|
|
|
|
QList<QSslError> errors;
|
2011-06-18 14:53:53 +00:00
|
|
|
// Setup the store with the default CA certificates
|
|
|
|
X509_STORE *certStore = q_X509_STORE_new();
|
|
|
|
if (!certStore) {
|
2014-12-08 12:35:47 +00:00
|
|
|
qCWarning(lcSsl) << "Unable to create certificate store";
|
2011-06-18 14:53:53 +00:00
|
|
|
errors << QSslError(QSslError::UnspecifiedError);
|
|
|
|
return errors;
|
|
|
|
}
|
2019-11-26 12:35:59 +00:00
|
|
|
const std::unique_ptr<X509_STORE, decltype(&q_X509_STORE_free)> storeGuard(certStore, q_X509_STORE_free);
|
2011-06-18 14:53:53 +00:00
|
|
|
|
2015-10-23 14:32:59 +00:00
|
|
|
const QDateTime now = QDateTime::currentDateTimeUtc();
|
2016-01-26 13:38:54 +00:00
|
|
|
for (const QSslCertificate &caCertificate : caCertificates) {
|
2014-05-01 22:10:21 +00:00
|
|
|
// From https://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html:
|
|
|
|
//
|
|
|
|
// If several CA certificates matching the name, key identifier, and
|
|
|
|
// serial number condition are available, only the first one will be
|
|
|
|
// examined. This may lead to unexpected results if the same CA
|
|
|
|
// certificate is available with different expiration dates. If a
|
|
|
|
// ``certificate expired'' verification error occurs, no other
|
|
|
|
// certificate will be searched. Make sure to not have expired
|
|
|
|
// certificates mixed with valid ones.
|
|
|
|
//
|
|
|
|
// See also: QSslContext::fromConfiguration()
|
2015-10-23 14:32:59 +00:00
|
|
|
if (caCertificate.expiryDate() >= now) {
|
2011-08-31 17:35:36 +00:00
|
|
|
q_X509_STORE_add_cert(certStore, reinterpret_cast<X509 *>(caCertificate.handle()));
|
2011-06-18 14:53:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-22 10:25:41 +00:00
|
|
|
QList<QSslErrorEntry> lastErrors;
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
if (!q_X509_STORE_set_ex_data(certStore, 0, &lastErrors)) {
|
|
|
|
qCWarning(lcSsl) << "Unable to attach external data (error list) to a store";
|
|
|
|
errors << QSslError(QSslError::UnspecifiedError);
|
|
|
|
return errors;
|
|
|
|
}
|
2011-06-18 14:53:53 +00:00
|
|
|
|
|
|
|
// Register a custom callback to get all verification errors.
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
q_X509_STORE_set_verify_cb(certStore, q_X509Callback);
|
2011-06-18 14:53:53 +00:00
|
|
|
|
|
|
|
// Build the chain of intermediate certificates
|
2018-08-07 08:29:46 +00:00
|
|
|
STACK_OF(X509) *intermediates = nullptr;
|
2011-06-18 14:53:53 +00:00
|
|
|
if (certificateChain.length() > 1) {
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null();
|
2011-06-18 14:53:53 +00:00
|
|
|
|
|
|
|
if (!intermediates) {
|
|
|
|
errors << QSslError(QSslError::UnspecifiedError);
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool first = true;
|
2016-01-26 13:38:54 +00:00
|
|
|
for (const QSslCertificate &cert : certificateChain) {
|
2011-06-18 14:53:53 +00:00
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
continue;
|
|
|
|
}
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
|
|
|
|
q_OPENSSL_sk_push((OPENSSL_STACK *)intermediates, reinterpret_cast<X509 *>(cert.handle()));
|
2011-06-18 14:53:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
X509_STORE_CTX *storeContext = q_X509_STORE_CTX_new();
|
|
|
|
if (!storeContext) {
|
|
|
|
errors << QSslError(QSslError::UnspecifiedError);
|
|
|
|
return errors;
|
|
|
|
}
|
2019-11-26 12:35:59 +00:00
|
|
|
std::unique_ptr<X509_STORE_CTX, decltype(&q_X509_STORE_CTX_free)> ctxGuard(storeContext, q_X509_STORE_CTX_free);
|
2011-06-18 14:53:53 +00:00
|
|
|
|
2011-08-31 17:35:36 +00:00
|
|
|
if (!q_X509_STORE_CTX_init(storeContext, certStore, reinterpret_cast<X509 *>(certificateChain[0].handle()), intermediates)) {
|
2011-06-18 14:53:53 +00:00
|
|
|
errors << QSslError(QSslError::UnspecifiedError);
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we can actually perform the verification of the chain we have built.
|
|
|
|
// We ignore the result of this function since we process errors via the
|
|
|
|
// callback.
|
|
|
|
(void) q_X509_verify_cert(storeContext);
|
2019-11-26 12:35:59 +00:00
|
|
|
ctxGuard.reset();
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
q_OPENSSL_sk_free((OPENSSL_STACK *)intermediates);
|
2011-06-18 14:53:53 +00:00
|
|
|
|
|
|
|
// Now process the errors
|
|
|
|
|
|
|
|
if (QSslCertificatePrivate::isBlacklisted(certificateChain[0])) {
|
|
|
|
QSslError error(QSslError::CertificateBlacklisted, certificateChain[0]);
|
|
|
|
errors << error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the certificate name against the hostname if one was specified
|
|
|
|
if ((!hostName.isEmpty()) && (!isMatchingHostname(certificateChain[0], hostName))) {
|
|
|
|
// No matches in common names or alternate names.
|
|
|
|
QSslError error(QSslError::HostNameMismatch, certificateChain[0]);
|
|
|
|
errors << error;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Translate errors from the error list into QSslErrors.
|
TLS socket: make verification callback lock-free (OpenSSL)
When our QSslSocketBackendPrivate (OpenSSL backend) was developed,
the ancient versions of OpenSSL did not have an API needed to pass
an application-specific data into verification callback. Thus the
developers resorted to the use of global variables (a list with errors)
and locks. Some of our auto-tests use QNAM and in-process server.
Whenever the client (essentially qhttpthreadeddelegate) and the server
live in different threads, any use of 'https' is dead-lock prone,
which recent events demonstrated and which were previously observed
but not understood properly (rare occasions, not always easy to
reproduce). Now we fix this for good by removing locking.
There are two places (in 5.12) where these locks are needed:
1. Before calling SSL_connect/SSL_accept (handshake) - here
we reuse the same trick we do in PSK callback ('SSL' has
an external data set, and it's 'this', meaning an object
of type QSslSocketBackendPrivate).
2. The static member function 'verify', here we do not have
'SSL', but we have our temporary 'X509_STORE', to which
we can directly attach an external data - a pointer to
a vector to collect verification errors.
Note, this change assumes that OpenSSL Qt is build/linked
against is at least of version 1.0.1 - we set external data
on SSL unconditionally (no version checks).
Fixes: QTBUG-76157
Change-Id: I05c98e77dfd5fb0c2c260fb6c463732facf53ffc
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2019-06-14 09:34:08 +00:00
|
|
|
errors.reserve(errors.size() + lastErrors.size());
|
|
|
|
for (const auto &error : qAsConst(lastErrors))
|
2016-01-17 13:16:43 +00:00
|
|
|
errors << _q_OpenSSL_to_QSslError(error.code, certificateChain.value(error.depth));
|
2011-06-18 14:53:53 +00:00
|
|
|
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
|
2014-11-21 11:44:33 +00:00
|
|
|
bool QSslSocketBackendPrivate::importPkcs12(QIODevice *device,
|
2014-05-10 21:49:37 +00:00
|
|
|
QSslKey *key, QSslCertificate *cert,
|
|
|
|
QList<QSslCertificate> *caCertificates,
|
|
|
|
const QByteArray &passPhrase)
|
|
|
|
{
|
|
|
|
if (!supportsSsl())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// These are required
|
|
|
|
Q_ASSERT(device);
|
|
|
|
Q_ASSERT(key);
|
|
|
|
Q_ASSERT(cert);
|
|
|
|
|
|
|
|
// Read the file into a BIO
|
|
|
|
QByteArray pkcs12data = device->readAll();
|
|
|
|
if (pkcs12data.size() == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
BIO *bio = q_BIO_new_mem_buf(const_cast<char *>(pkcs12data.constData()), pkcs12data.size());
|
|
|
|
|
|
|
|
// Create the PKCS#12 object
|
2018-08-07 08:29:46 +00:00
|
|
|
PKCS12 *p12 = q_d2i_PKCS12_bio(bio, nullptr);
|
2014-05-10 21:49:37 +00:00
|
|
|
if (!p12) {
|
2018-08-07 08:29:46 +00:00
|
|
|
qCWarning(lcSsl, "Unable to read PKCS#12 structure, %s",
|
|
|
|
q_ERR_error_string(q_ERR_get_error(), nullptr));
|
2014-05-10 21:49:37 +00:00
|
|
|
q_BIO_free(bio);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract the data
|
2017-08-07 10:49:59 +00:00
|
|
|
EVP_PKEY *pkey = nullptr;
|
2014-05-10 21:49:37 +00:00
|
|
|
X509 *x509;
|
2018-08-07 08:29:46 +00:00
|
|
|
STACK_OF(X509) *ca = nullptr;
|
2014-05-10 21:49:37 +00:00
|
|
|
|
|
|
|
if (!q_PKCS12_parse(p12, passPhrase.constData(), &pkey, &x509, &ca)) {
|
2018-08-07 08:29:46 +00:00
|
|
|
qCWarning(lcSsl, "Unable to parse PKCS#12 structure, %s",
|
|
|
|
q_ERR_error_string(q_ERR_get_error(), nullptr));
|
2014-05-10 21:49:37 +00:00
|
|
|
q_PKCS12_free(p12);
|
|
|
|
q_BIO_free(bio);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to Qt types
|
|
|
|
if (!key->d->fromEVP_PKEY(pkey)) {
|
2014-12-08 12:35:47 +00:00
|
|
|
qCWarning(lcSsl, "Unable to convert private key");
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
q_OPENSSL_sk_pop_free(reinterpret_cast<OPENSSL_STACK *>(ca),
|
2018-05-03 12:22:49 +00:00
|
|
|
reinterpret_cast<void (*)(void *)>(q_X509_free));
|
2014-05-10 21:49:37 +00:00
|
|
|
q_X509_free(x509);
|
|
|
|
q_EVP_PKEY_free(pkey);
|
|
|
|
q_PKCS12_free(p12);
|
|
|
|
q_BIO_free(bio);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
*cert = QSslCertificatePrivate::QSslCertificate_from_X509(x509);
|
|
|
|
|
|
|
|
if (caCertificates)
|
|
|
|
*caCertificates = QSslSocketBackendPrivate::STACKOFX509_to_QSslCertificates(ca);
|
|
|
|
|
|
|
|
// Clean up
|
QSslSocket: OpenSSL 1.1 backend
This patch-set implements a new QSslSocket backend based on OpenSSL 1.1.
1. General.
The code in this patch was organized to achieve these (somewhat contradicting)
objectives:
- keep the new code free of #if-ery, as far as possible;
- make it easy to clean away dead code when we're eventually able to retire
out-dated OpenSSL versions;
- reduce the amount of code duplication.
If changes in some file/component were insignificant (~5 one-liners per file),
we still use pp-checks like: #if QT_CONFIG(opensslv11) ... #else ... #endif -
the logic is simple and it's still easy to clean the code if we remove the legacy
back-end. Where it saved #if-ery, we also introduced 'forward-compatible'
macros implementing equivalents of 1.1 functions using older OpenSSL.
In case some class contains a lot of version-specific ifdefs (particularly where
nested #if-ery was complex) we choose to split code into: "pre11" h/cpp files,
"shared" h/cpp files (they preserve their original names, e.g qsslsocket_openssl.cpp)
and "11" h/cpp files. If in future we remove the legacy back-end, "pre11" should be
removed; "shared" and "11" parts - merged.
2. Configuration.
We introduced a new feature 'opensslv11' which complements the pre-existing
'openssl' and 'openssl-linked' features. The 'opensslv11' feature is enabled
by a simple test which either compiles successfully or ends in a compilation
error, depending on a value of the OPENSSL_VERSION_NUMBER constant. If the
feature was enabled, we also append an additional compilation flag
-DOPENSSL_API_COMPAT=0x10100000L to make sure our new code does not contain
deprecated structures, function calls, macro-invocations from OpenSSL < 1.1.
Change-Id: I2064efbe9685def5d2bb2233a66f7581954fb74a
Reviewed-by: André Klitzing <aklitzing@gmail.com>
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
2017-03-23 11:43:22 +00:00
|
|
|
q_OPENSSL_sk_pop_free(reinterpret_cast<OPENSSL_STACK *>(ca),
|
|
|
|
reinterpret_cast<void (*)(void *)>(q_X509_free));
|
|
|
|
|
2014-05-10 21:49:37 +00:00
|
|
|
q_X509_free(x509);
|
|
|
|
q_EVP_PKEY_free(pkey);
|
|
|
|
q_PKCS12_free(p12);
|
|
|
|
q_BIO_free(bio);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-04-27 10:05:43 +00:00
|
|
|
QT_END_NAMESPACE
|