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