Add class QOcspResponse, providing a bit more details

about why a certificate was revoked (if it was) and the responder's
certificate, if we managed to verify a signature, as was previously
shortly discussed in the 'OCSP stapling' patch-set. Auto-test update
will be in a separate patch.

[ChangeLog][QtNetwork][Ssl] Added class QOcspResponse as a part of
 OCSP stapling support.

Change-Id: I4e17fb6fc4c3dae0b8ad04ff2897a4823736d16e
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
bb10
Timur Pocheptsov 2019-01-16 14:43:09 +01:00
parent 237c3972fd
commit e649c4143e
8 changed files with 484 additions and 5 deletions

View File

@ -0,0 +1,238 @@
/****************************************************************************
** Copyright (C) 2011 Richard J. Moore <rich@kde.org>
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qocspresponse_p.h"
#include "qocspresponse.h"
QT_BEGIN_NAMESPACE
/*!
\class QOcspResponse
\brief This class represents Online Certificate Status Protocol response
\since 5.13
\ingroup network
\ingroup ssl
\inmodule QtNetwork
The QOcspResponse class represents the revocation status of a server's certficate,
received by the client-side socket during the TLS handshake. QSslSocket must be
configured with OCSP stapling enabled. A non-empty response corresponds to the
certificate that can be obtained from QSslConfiguration::peerCertificate().
\sa QSslSocket, QSslSocket::ocspResponse(), isNull(), clear(), certificateStatus(),
revocationReason(), responder(), OcspCertificateStatus, OcspRevocationReason,
QSslConfiguration::setOcspStaplingEnabled(), QSslConfiguration::ocspStaplingEnabled(),
QSslConfiguration::peerCertificate()
*/
/*!
\enum OcspCertificateStatus
\brief Describes the Online Certificate Status
\relates QOcspResponse
\since 5.13
\ingroup network
\ingroup ssl
\inmodule QtNetwork
\value Good The certificate is not revoked, but this does not necessarily
mean that the certificate was ever issued or that the time at which
the response was produced is within the certificate's validity interval.
\value Revoked This state indicates that the certificate has been revoked
(either permanently or temporarily - on hold).
\value Unknown This state indicates that the responder doesn't know about
the certificate being requested.
\sa OcspRevocationReason
*/
/*!
\enum OcspRevocationReason
\brief Describes the reason for revocation
\relates QOcspResponse
\since 5.13
\ingroup network
\ingroup ssl
\inmodule QtNetwork
This enumeration describes revocation reasons, defined in \l{https://tools.ietf.org/html/rfc5280#section-5.3.1}{RFC 5280, section 5.3.1}
\value None
\value Unspecified
\value KeyCompromise
\value CACompromise
\value AffiliationChanged
\value Superseded
\value CessationOfOperation
\value CertificateHold
\value RemoveFromCRL
*/
/*!
\since 5.13
Creates a new, null OCSP response.
\sa isNull()
*/
QOcspResponse::QOcspResponse()
: d(new QOcspResponsePrivate)
{
}
/*!
\since 5.13
Creates a new response, the copy of \a other.
*/
QOcspResponse::QOcspResponse(const QOcspResponse &other)
{
*d = *other.d;
}
/*!
\since 5.13
Move-constructs a QOcspResponse instance.
*/
QOcspResponse::QOcspResponse(QOcspResponse &&other) Q_DECL_NOTHROW
{
d.swap(other.d);
}
/*!
\since 5.13
Destroys the response.
*/
QOcspResponse::~QOcspResponse()
{
}
/*!
\since 5.13
Assignes \a other to the response and returns a reference to this response.
*/
QOcspResponse &QOcspResponse::operator=(const QOcspResponse &other)
{
if (this != &other)
*d = *other.d;
return *this;
}
/*!
\since 5.13
Move-assigns \a other to this QOcspResponse instance.
*/
QOcspResponse &QOcspResponse::operator=(QOcspResponse &&other) Q_DECL_NOTHROW
{
if (this != &other)
d.swap(other.d);
return *this;
}
/*!
\since 5.13
Returns \c true for default-constructed OCSP responses and also if during a
handshake no definitive OCSP response, or no response was received at all.
\sa QOcspResponse(), QSslSocket::ocspResponse()
*/
bool QOcspResponse::isNull() const
{
return d->isNull;
}
/*!
\since 5.13
Resets this QOcspResponse to its default, null state.
\sa QOcspResponse(), isNull()
*/
void QOcspResponse::clear()
{
d->certificateStatus = OcspCertificateStatus::Unknown;
d->revocationReason = OcspRevocationReason::None;
d->isNull = true;
d->signerCert.clear();
}
/*!
\since 5.13
Returns the certificate status.
\sa OcspCertificateStatus
*/
OcspCertificateStatus QOcspResponse::certificateStatus() const
{
return d->certificateStatus;
}
/*!
\since 5.13
Returns the reason for revocation.
*/
OcspRevocationReason QOcspResponse::revocationReason() const
{
return d->revocationReason;
}
/*!
\since 5.13
This function returns a certificate used to sign OCSP response.
*/
QSslCertificate QOcspResponse::responder() const
{
return d->signerCert;
}
QT_END_NAMESPACE

View File

@ -0,0 +1,103 @@
/****************************************************************************
** Copyright (C) 2011 Richard J. Moore <rich@kde.org>
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QOCSPRESPONSE_H
#define QOCSPRESPONSE_H
#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qscopedpointer.h>
#ifndef Q_CLANG_QDOC
QT_REQUIRE_CONFIG(ssl);
#endif
QT_BEGIN_NAMESPACE
enum class OcspCertificateStatus
{
Good,
Revoked,
Unknown
};
enum class OcspRevocationReason
{
None = -1,
Unspecified,
KeyCompromise,
CACompromise,
AffiliationChanged,
Superseded,
CessationOfOperation,
CertificateHold,
RemoveFromCRL
};
class QOcspResponsePrivate;
class QOcspResponse
{
public:
QOcspResponse();
QOcspResponse(const QOcspResponse &other);
QOcspResponse(QOcspResponse && other) Q_DECL_NOEXCEPT;
~QOcspResponse();
QOcspResponse &operator = (const QOcspResponse &other);
QOcspResponse &operator = (QOcspResponse &&other) Q_DECL_NOTHROW;
bool isNull() const;
void clear();
OcspCertificateStatus certificateStatus() const;
OcspRevocationReason revocationReason() const;
class QSslCertificate responder() const;
private:
friend class QSslSocketBackendPrivate;
QScopedPointer<QOcspResponsePrivate> d;
};
QT_END_NAMESPACE
#endif // QOCSPRESPONSE_H

View File

@ -0,0 +1,73 @@
/****************************************************************************
** Copyright (C) 2011 Richard J. Moore <rich@kde.org>
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtNetwork module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QOCSPRESPONSE_P_H
#define QOCSPRESPONSE_P_H
#include <private/qtnetworkglobal_p.h>
#include <qsslcertificate.h>
#include <qocspresponse.h>
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
QT_BEGIN_NAMESPACE
class QOcspResponsePrivate
{
public:
OcspCertificateStatus certificateStatus = OcspCertificateStatus::Unknown;
OcspRevocationReason revocationReason = OcspRevocationReason::None;
bool isNull = true;
QSslCertificate signerCert;
};
QT_END_NAMESPACE
#endif // QOCSPRESPONSE_P_H

View File

@ -314,6 +314,7 @@
#include "qssl_p.h"
#include "qsslsocket.h"
#include "qsslcipher.h"
#include "qocspresponse.h"
#ifndef QT_NO_OPENSSL
#include "qsslsocket_openssl_p.h"
#endif
@ -1151,6 +1152,20 @@ QSsl::SslProtocol QSslSocket::sessionProtocol() const
return d->sessionProtocol();
}
/*!
\since 5.13
This function returns Online Certificate Status Protocol response that
a server may send during a TLS handshake using OCSP stapling. If no
definitive or no response was received at all, the response is empty.
\sa QSslConfiguration::setOcspStaplingEnabled(), QOcspResponse::isNull()
*/
QOcspResponse QSslSocket::ocspResponse() const
{
Q_D(const QSslSocket);
return d->ocspResponse;
}
/*!
Sets the socket's private \l {QSslKey} {key} to \a key. The
@ -2135,6 +2150,7 @@ void QSslSocketPrivate::init()
shutdown = false;
pendingClose = false;
flushTriggered = false;
ocspResponse.clear();
// we don't want to clear the ignoreErrorsList, so
// that it is possible setting it before connecting

View File

@ -60,6 +60,7 @@ class QSslCertificate;
class QSslConfiguration;
class QSslEllipticCurve;
class QSslPreSharedKeyAuthenticator;
class QOcspResponse;
class QSslSocketPrivate;
class Q_NETWORK_EXPORT QSslSocket : public QTcpSocket
@ -142,6 +143,7 @@ public:
QList<QSslCertificate> peerCertificateChain() const;
QSslCipher sessionCipher() const;
QSsl::SslProtocol sessionProtocol() const;
QOcspResponse ocspResponse() const;
// Private keys, for server sockets.
void setPrivateKey(const QSslKey &key);

View File

@ -65,6 +65,7 @@
#include "qsslellipticcurve.h"
#include "qsslpresharedkeyauthenticator.h"
#include "qsslpresharedkeyauthenticator_p.h"
#include "qocspresponse_p.h"
#ifdef Q_OS_WIN
#include "qwindowscarootfetcher_p.h"
@ -257,6 +258,34 @@ QSslError qt_OCSP_response_status_to_QSslError(long code)
Q_UNREACHABLE();
}
OcspRevocationReason qt_OCSP_revocation_reason(int reason)
{
switch (reason) {
case OCSP_REVOKED_STATUS_NOSTATUS:
return OcspRevocationReason::None;
case OCSP_REVOKED_STATUS_UNSPECIFIED:
return OcspRevocationReason::Unspecified;
case OCSP_REVOKED_STATUS_KEYCOMPROMISE:
return OcspRevocationReason::KeyCompromise;
case OCSP_REVOKED_STATUS_CACOMPROMISE:
return OcspRevocationReason::CACompromise;
case OCSP_REVOKED_STATUS_AFFILIATIONCHANGED:
return OcspRevocationReason::AffiliationChanged;
case OCSP_REVOKED_STATUS_SUPERSEDED:
return OcspRevocationReason::Superseded;
case OCSP_REVOKED_STATUS_CESSATIONOFOPERATION:
return OcspRevocationReason::CessationOfOperation;
case OCSP_REVOKED_STATUS_CERTIFICATEHOLD:
return OcspRevocationReason::CertificateHold;
case OCSP_REVOKED_STATUS_REMOVEFROMCRL:
return OcspRevocationReason::RemoveFromCRL;
default:
return OcspRevocationReason::None;
}
Q_UNREACHABLE();
}
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
@ -1429,6 +1458,9 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
Q_ASSERT(mode == QSslSocket::SslClientMode); // See initSslContext() for SslServerMode
Q_ASSERT(configuration.peerVerifyMode != QSslSocket::VerifyNone);
ocspResponse.clear();
QOcspResponsePrivate *dResponse = ocspResponse.d.data();
ocspErrorDescription.clear();
ocspErrors.clear();
@ -1524,9 +1556,10 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
// 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.
dResponse->isNull = false;
bool matchFound = false;
if (configuration.peerCertificate.isSelfSigned()) {
dResponse->signerCert = configuration.peerCertificate;
matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, peerX509);
} else {
const STACK_OF(X509) *certs = q_SSL_get_peer_cert_chain(ssl);
@ -1541,16 +1574,20 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
X509 *issuer = q_sk_X509_value(certs, i);
matchFound = qt_OCSP_certificate_match(singleResponse, peerX509, issuer);
if (matchFound) {
if (q_X509_check_issued(issuer, peerX509) == X509_V_OK)
if (q_X509_check_issued(issuer, peerX509) == X509_V_OK) {
dResponse->signerCert = QSslCertificatePrivate::QSslCertificate_from_X509(issuer);
break;
}
matchFound = false;
}
}
}
}
if (!matchFound)
if (!matchFound) {
dResponse->signerCert.clear();
ocspErrors.push_back({QSslError::OcspResponseCertIdUnknown, configuration.peerCertificate});
}
// Check if the response is valid time-wise:
ASN1_GENERALIZEDTIME *revTime = nullptr;
@ -1562,6 +1599,7 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
// This is unexpected, treat as SslHandshakeError, OCSP_check_validity assumes this pointer
// to be != nullptr.
ocspErrors.clear();
ocspResponse.clear();
ocspErrorDescription = QSslSocket::tr("Failed to extract 'this update time' from the SingleResponse");
return false;
}
@ -1582,11 +1620,15 @@ bool QSslSocketBackendPrivate::checkOcspStatus()
switch (certStatus) {
case V_OCSP_CERTSTATUS_GOOD:
// This certificate was not found among the revoked ones.
dResponse->certificateStatus = OcspCertificateStatus::Good;
break;
case V_OCSP_CERTSTATUS_REVOKED:
dResponse->certificateStatus = OcspCertificateStatus::Revoked;
dResponse->revocationReason = qt_OCSP_revocation_reason(reason);
ocspErrors.push_back({QSslError::CertificateRevoked, configuration.peerCertificate});
break;
case V_OCSP_CERTSTATUS_UNKNOWN:
dResponse->certificateStatus = OcspCertificateStatus::Unknown;
ocspErrors.push_back({QSslError::OcspStatusUnknown, configuration.peerCertificate});
}

View File

@ -58,6 +58,7 @@
#include <private/qtcpsocket_p.h>
#include "qsslkey.h"
#include "qsslconfiguration_p.h"
#include "qocspresponse.h"
#ifndef QT_NO_OPENSSL
#include <private/qsslcontext_openssl_p.h>
#else
@ -207,6 +208,7 @@ protected:
bool verifyErrorsHaveBeenIgnored();
bool paused;
bool flushTriggered;
QOcspResponse ocspResponse;
};
#if QT_CONFIG(securetransport) || QT_CONFIG(schannel)

View File

@ -29,7 +29,9 @@ qtConfig(ssl) {
ssl/qsslsocket.h \
ssl/qsslsocket_p.h \
ssl/qsslpresharedkeyauthenticator.h \
ssl/qsslpresharedkeyauthenticator_p.h
ssl/qsslpresharedkeyauthenticator_p.h \
ssl/qocspresponse.h \
ssl/qocspresponse_p.h
SOURCES += ssl/qsslconfiguration.cpp \
ssl/qsslcipher.cpp \
ssl/qssldiffiehellmanparameters.cpp \
@ -37,7 +39,8 @@ qtConfig(ssl) {
ssl/qsslkey_p.cpp \
ssl/qsslerror.cpp \
ssl/qsslsocket.cpp \
ssl/qsslpresharedkeyauthenticator.cpp
ssl/qsslpresharedkeyauthenticator.cpp \
ssl/qocspresponse.cpp
winrt {
HEADERS += ssl/qsslsocket_winrt_p.h