Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwindowscarootfetcher.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5#include "qx509_openssl_p.h"
6#include "qopenssl_p.h"
7
8#include <QtCore/QThread>
9#include <QtGlobal>
10
11#include <QtCore/qscopeguard.h>
12
13#ifdef QSSLSOCKET_DEBUG
14#include <QtNetwork/private/qtlsbackend_p.h> // for debug categories
15#include <QtCore/QElapsedTimer>
16#endif
17
19
21{
22public:
24 {
25 qRegisterMetaType<QSslCertificate>();
26 setObjectName(QStringLiteral("QWindowsCaRootFetcher"));
27 start();
28 }
30 {
31 quit();
32 wait(15500); // worst case, a running request can block for 15 seconds
33 }
34};
35
36Q_GLOBAL_STATIC(QWindowsCaRootFetcherThread, windowsCaRootFetcherThread);
37
38namespace {
39
40const QList<QSslCertificate> buildVerifiedChain(const QList<QSslCertificate> &caCertificates,
41 PCCERT_CHAIN_CONTEXT chainContext,
42 const QString &peerVerifyName)
43{
44 // We ended up here because OpenSSL verification failed to
45 // build a chain, with intermediate certificate missing
46 // but "Authority Information Access" extension present.
47 // Also, apparently the normal CA fetching path was disabled
48 // by setting custom CA certificates. We convert wincrypt's
49 // structures in QSslCertificate and give OpenSSL the second
50 // chance to verify the now (apparently) complete chain.
51 // In addition, wincrypt gives us a benefit of some checks
52 // we don't have in OpenSSL back-end.
53 Q_ASSERT(chainContext);
54
55 if (!chainContext->cChain)
56 return {};
57
58 QList<QSslCertificate> verifiedChain;
59
60 CERT_SIMPLE_CHAIN *chain = chainContext->rgpChain[chainContext->cChain - 1];
61 if (!chain)
62 return {};
63
64 if (chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN)
65 return {}; // No need to mess with OpenSSL (the chain is still incomplete).
66
67 for (DWORD i = 0; i < chain->cElement; ++i) {
68 CERT_CHAIN_ELEMENT *element = chain->rgpElement[i];
69 QSslCertificate cert(QByteArray(reinterpret_cast<const char*>(element->pCertContext->pbCertEncoded),
70 int(element->pCertContext->cbCertEncoded)), QSsl::Der);
71
72 if (cert.isBlacklisted())
73 return {};
74
75 if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_REVOKED) // Good to know!
76 return {};
77
78 if (element->TrustStatus.dwErrorStatus & CERT_TRUST_IS_NOT_SIGNATURE_VALID)
79 return {};
80
81 verifiedChain.append(cert);
82 }
83
84 // We rely on OpenSSL's ability to find other problems.
85 const auto tlsErrors = QTlsPrivate::X509CertificateOpenSSL::verify(caCertificates, verifiedChain, peerVerifyName);
86 if (tlsErrors.size())
87 verifiedChain.clear();
88
89 return verifiedChain;
90}
91
92} // unnamed namespace
93
95 const QList<QSslCertificate> &caCertificates, const QString &hostName)
96 : cert(certificate), mode(sslMode), explicitlyTrustedCAs(caCertificates), peerVerifyName(hostName)
97{
98 moveToThread(windowsCaRootFetcherThread());
99}
100
104
106{
107 QByteArray der = cert.toDer();
108 PCCERT_CONTEXT wincert = CertCreateCertificateContext(X509_ASN_ENCODING, (const BYTE *)der.constData(), der.length());
109 if (!wincert) {
110#ifdef QSSLSOCKET_DEBUG
111 qCDebug(lcTlsBackend, "QWindowsCaRootFetcher failed to convert certificate to windows form");
112#endif
114 deleteLater();
115 return;
116 }
117
118 CERT_CHAIN_PARA parameters;
119 memset(&parameters, 0, sizeof(parameters));
120 parameters.cbSize = sizeof(parameters);
121 // set key usage constraint
122 parameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
123 parameters.RequestedUsage.Usage.cUsageIdentifier = 1;
124 LPSTR oid = (LPSTR)(mode == QSslSocket::SslClientMode ? szOID_PKIX_KP_SERVER_AUTH : szOID_PKIX_KP_CLIENT_AUTH);
125 parameters.RequestedUsage.Usage.rgpszUsageIdentifier = &oid;
126
127#ifdef QSSLSOCKET_DEBUG
128 QElapsedTimer stopwatch;
129 stopwatch.start();
130#endif
131 PCCERT_CHAIN_CONTEXT chain;
132 auto additionalStore = createAdditionalStore();
133 BOOL result = CertGetCertificateChain(
134 nullptr, //default engine
135 wincert,
136 nullptr, //current date/time
137 additionalStore.get(), //default store (nullptr) or CAs an application trusts
138 &parameters,
139 0, //default dwFlags
140 nullptr, //reserved
141 &chain);
142#ifdef QSSLSOCKET_DEBUG
143 qCDebug(lcSsl) << "QWindowsCaRootFetcher" << stopwatch.elapsed() << "ms to get chain";
144#endif
145
146 QSslCertificate trustedRoot;
147 if (result) {
148#ifdef QSSLSOCKET_DEBUG
149 qCDebug(lcSsl) << "QWindowsCaRootFetcher - examining windows chains";
150 if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
151 qCDebug(lcSsl) << " - TRUSTED";
152 else
153 qCDebug(lcSsl) << " - NOT TRUSTED" << chain->TrustStatus.dwErrorStatus;
154 if (chain->TrustStatus.dwInfoStatus & CERT_TRUST_IS_SELF_SIGNED)
155 qCDebug(lcSsl) << " - SELF SIGNED";
156 qCDebug(lcSsl) << "QWindowsCaRootFetcher - dumping simple chains";
157 for (unsigned int i = 0; i < chain->cChain; i++) {
158 if (chain->rgpChain[i]->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
159 qCDebug(lcSsl) << " - TRUSTED SIMPLE CHAIN" << i;
160 else
161 qCDebug(lcSsl) << " - UNTRUSTED SIMPLE CHAIN" << i << "reason:" << chain->rgpChain[i]->TrustStatus.dwErrorStatus;
162 for (unsigned int j = 0; j < chain->rgpChain[i]->cElement; j++) {
163 QSslCertificate foundCert(QByteArray((const char *)chain->rgpChain[i]->rgpElement[j]->pCertContext->pbCertEncoded
164 , chain->rgpChain[i]->rgpElement[j]->pCertContext->cbCertEncoded), QSsl::Der);
165 qCDebug(lcSsl) << " - " << foundCert;
166 }
167 }
168 qCDebug(lcSsl) << " - and" << chain->cLowerQualityChainContext << "low quality chains"; //expect 0, we haven't asked for them
169#endif
170
171 //based on http://msdn.microsoft.com/en-us/library/windows/desktop/aa377182%28v=vs.85%29.aspx
172 //about the final chain rgpChain[cChain-1] which must begin with a trusted root to be valid
173 if (chain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR
174 && chain->cChain > 0) {
175 const PCERT_SIMPLE_CHAIN finalChain = chain->rgpChain[chain->cChain - 1];
176 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa377544%28v=vs.85%29.aspx
177 // rgpElement[0] is the end certificate chain element. rgpElement[cElement-1] is the self-signed "root" certificate element.
178 if (finalChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR
179 && finalChain->cElement > 0) {
180 trustedRoot = QSslCertificate(QByteArray((const char *)finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->pbCertEncoded
181 , finalChain->rgpElement[finalChain->cElement - 1]->pCertContext->cbCertEncoded), QSsl::Der);
182 }
183 } else if (explicitlyTrustedCAs.size()) {
184 // Setting custom CA in configuration, and those CAs are not trusted by MS.
185#if QT_CONFIG(openssl)
186 const auto verifiedChain = buildVerifiedChain(explicitlyTrustedCAs, chain, peerVerifyName);
187 if (verifiedChain.size())
188 trustedRoot = verifiedChain.last();
189#else
190 //It's only OpenSSL code-path that can trigger such a fetch.
191 Q_UNREACHABLE();
192#endif
193 }
194 CertFreeCertificateChain(chain);
195 }
196 CertFreeCertificateContext(wincert);
197
198 emit finished(cert, trustedRoot);
199 deleteLater();
200}
201
202QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const
203{
204 QHCertStorePointer customStore;
205 if (explicitlyTrustedCAs.isEmpty())
206 return customStore;
207
208 if (HCERTSTORE rawPtr = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, nullptr)) {
209 customStore.reset(rawPtr);
210
211 unsigned rootsAdded = 0;
212 for (const QSslCertificate &caCert : explicitlyTrustedCAs) {
213 const auto der = caCert.toDer();
214 PCCERT_CONTEXT winCert = CertCreateCertificateContext(X509_ASN_ENCODING,
215 reinterpret_cast<const BYTE *>(der.data()),
216 DWORD(der.length()));
217 if (!winCert) {
218#if defined(QSSLSOCKET_DEBUG)
219 qCWarning(lcSsl) << "CA fetcher, failed to convert QSslCertificate"
220 << "to the native representation";
221#endif // QSSLSOCKET_DEBUG
222 continue;
223 }
224 const auto deleter = qScopeGuard([winCert](){
225 CertFreeCertificateContext(winCert);
226 });
227 if (CertAddCertificateContextToStore(customStore.get(), winCert, CERT_STORE_ADD_ALWAYS, nullptr))
228 ++rootsAdded;
229#if defined(QSSLSOCKET_DEBUG)
230 else //Why assert? With flags we're using and winCert check - should not happen!
231 Q_ASSERT("CertAddCertificateContextToStore() failed");
232#endif // QSSLSOCKET_DEBUG
233 }
234 if (!rootsAdded) //Useless store, no cert was added.
235 customStore.reset();
236#if defined(QSSLSOCKET_DEBUG)
237 } else {
238
239 qCWarning(lcSsl) << "CA fetcher, failed to create a custom"
240 << "store for explicitly trusted CA certificate";
241#endif // QSSLSOCKET_DEBUG
242 }
243
244 return customStore;
245}
246
248
249#include "moc_qwindowscarootfetcher_p.cpp"
\inmodule QtCore
Definition qbytearray.h:57
\inmodule QtCore
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
qsizetype size() const noexcept
Definition qlist.h:397
bool isEmpty() const noexcept
Definition qlist.h:401
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
The QSslCertificate class provides a convenient API for an X509 certificate.
QByteArray toDer() const
Returns this certificate converted to a DER (binary) encoded representation.
SslMode
Describes the connection modes available for QSslSocket.
Definition qsslsocket.h:33
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
void quit()
Definition qthread.cpp:1008
static QList< QSslError > verify(const QList< QSslCertificate > &chain, const QString &hostName)
void finished(QSslCertificate brokenChain, QSslCertificate caroot)
QWindowsCaRootFetcher(const QSslCertificate &certificate, QSslSocket::SslMode sslMode, const QList< QSslCertificate > &caCertificates={}, const QString &hostName={})
@ Der
Definition qssl.h:30
Combined button and popup list for selecting options.
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
GLenum mode
GLuint start
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define QStringLiteral(str)
#define emit
std::unique_ptr< void, QHCertStoreDeleter > QHCertStorePointer
Definition qwincrypt_p.h:41
QList< QSslCertificate > cert
[0]