summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKai Uwe Broulik <[email protected]>2025-05-20 08:29:58 +0200
committerKai Uwe Broulik <[email protected]>2025-05-27 09:16:17 +0200
commit671afae06e9f9d228e9563c7c60a31456d2b2741 (patch)
tree700fc0846fb05b93bc126ddf20962cd7211140c8
parent5c6200c971b5478c08023f4233f248f70e09e3d7 (diff)
Add API for providing additional CA certificatesHEADdev
This allows to provide additional certificates that are considered when verifying a certificate. This is done by the cert verifier downstream of the platform-specific cert store. Fixes: QTBUG-50586 Change-Id: Ie90547f1013f22f994aaff536fadf906a44a88ef Reviewed-by: Moss Heim <[email protected]>
-rw-r--r--REUSE.toml1
-rw-r--r--src/core/api/qwebengineprofile.cpp13
-rw-r--r--src/core/api/qwebengineprofile.h2
-rw-r--r--src/core/api/qwebengineprofilebuilder.cpp15
-rw-r--r--src/core/api/qwebengineprofilebuilder.h2
-rw-r--r--src/core/api/qwebengineprofilebuilder_p.h1
-rw-r--r--src/core/profile_adapter.cpp9
-rw-r--r--src/core/profile_adapter.h5
-rw-r--r--src/core/profile_io_data_qt.cpp14
-rw-r--r--src/webenginequick/api/qquickwebengineprofileprototype.cpp48
-rw-r--r--src/webenginequick/api/qquickwebengineprofileprototype_p.h11
-rw-r--r--src/webenginequick/api/qquickwebengineprofileprototype_p_p.h3
-rw-r--r--tests/auto/quick/publicapi/tst_publicapi.cpp1
-rw-r--r--tests/auto/widgets/qwebengineprofilebuilder/CMakeLists.txt13
-rw-r--r--tests/auto/widgets/qwebengineprofilebuilder/resources/ca.pem24
-rw-r--r--tests/auto/widgets/qwebengineprofilebuilder/resources/server.key28
-rw-r--r--tests/auto/widgets/qwebengineprofilebuilder/resources/server.pem25
-rw-r--r--tests/auto/widgets/qwebengineprofilebuilder/tst_qwebengineprofilebuilder.cpp98
18 files changed, 308 insertions, 5 deletions
diff --git a/REUSE.toml b/REUSE.toml
index faaa2093d..386f0741f 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -23,6 +23,7 @@ path = ["tests/auto/widgets/qwebenginepage/resources/*",
"tests/auto/widgets/qwebenginescript/resources/*",
"tests/auto/widgets/qwebengineview/resources/*",
"tests/auto/widgets/qwebengineprofile/resources/*",
+ "tests/auto/widgets/qwebengineprofilebuilder/resources/*",
"tests/auto/widgets/qwebenginehistory/resources/*",
"tests/auto/core/certificateerror/resources/*",
"tests/auto/core/origins/resources/subdir/*",
diff --git a/src/core/api/qwebengineprofile.cpp b/src/core/api/qwebengineprofile.cpp
index 4a26703b6..ceeff460a 100644
--- a/src/core/api/qwebengineprofile.cpp
+++ b/src/core/api/qwebengineprofile.cpp
@@ -912,6 +912,19 @@ QWebEngineClientCertificateStore *QWebEngineProfile::clientCertificateStore()
}
/*!
+ \since 6.10
+
+ Returns additional trusted certificates in this profile's CA certificate database.
+
+ \sa QWebEngineProfileBuilder::setAdditionalTrustedCertificates()
+*/
+QList<QSslCertificate> QWebEngineProfile::additionalTrustedCertificates() const
+{
+ Q_D(const QWebEngineProfile);
+ return d->profileAdapter()->additionalTrustedCertificates();
+}
+
+/*!
* Requests an icon for a previously loaded page with this profile from the database. Each profile
* has its own icon database and it is stored in the persistent storage thus the stored icons
* can be accessed without network connection too. The icon must be previously loaded to be
diff --git a/src/core/api/qwebengineprofile.h b/src/core/api/qwebengineprofile.h
index 8be367fb1..a596c8e46 100644
--- a/src/core/api/qwebengineprofile.h
+++ b/src/core/api/qwebengineprofile.h
@@ -16,6 +16,7 @@
QT_BEGIN_NAMESPACE
+class QSslCertificate;
class QUrl;
class QWebEngineClientCertificateStore;
class QWebEngineClientHints;
@@ -117,6 +118,7 @@ public:
void setNotificationPresenter(std::function<void(std::unique_ptr<QWebEngineNotification>)> notificationPresenter);
QWebEngineClientCertificateStore *clientCertificateStore();
+ QList<QSslCertificate> additionalTrustedCertificates() const;
void requestIconForPageURL(const QUrl &url, int desiredSizeInPixel, std::function<void(const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback) const;
void requestIconForIconURL(const QUrl &url, int desiredSizeInPixel, std::function<void(const QIcon &, const QUrl &)> iconAvailableCallback) const;
diff --git a/src/core/api/qwebengineprofilebuilder.cpp b/src/core/api/qwebengineprofilebuilder.cpp
index 91a604ba1..9adc12dac 100644
--- a/src/core/api/qwebengineprofilebuilder.cpp
+++ b/src/core/api/qwebengineprofilebuilder.cpp
@@ -88,7 +88,8 @@ QWebEngineProfile *QWebEngineProfileBuilder::createProfile(const QString &storag
d_ptr->m_persistentCookiesPolicy),
d_ptr->m_httpCacheMaxSize,
QtWebEngineCore::ProfileAdapter::PersistentPermissionsPolicy(
- d_ptr->m_persistentPermissionPolicy))),
+ d_ptr->m_persistentPermissionPolicy),
+ d_ptr->m_additionalTrustedCertificates)),
parent);
}
@@ -174,3 +175,15 @@ QWebEngineProfileBuilder &QWebEngineProfileBuilder::setPersistentPermissionsPoli
d_ptr->m_persistentPermissionPolicy = persistentPermissionPolicy;
return *this;
}
+
+/*!
+ \since 6.10
+
+ Sets additional certificates for this profile's CA certificate database to \a certificates.
+*/
+QWebEngineProfileBuilder &QWebEngineProfileBuilder::setAdditionalTrustedCertificates(
+ const QList<QSslCertificate> &certificates)
+{
+ d_ptr->m_additionalTrustedCertificates = certificates;
+ return *this;
+}
diff --git a/src/core/api/qwebengineprofilebuilder.h b/src/core/api/qwebengineprofilebuilder.h
index d81a6bd47..1a03d3aa1 100644
--- a/src/core/api/qwebengineprofilebuilder.h
+++ b/src/core/api/qwebengineprofilebuilder.h
@@ -27,6 +27,8 @@ public:
Q_WEBENGINECORE_EXPORT QWebEngineProfileBuilder &setHttpCacheMaximumSize(int maxSizeInBytes);
Q_WEBENGINECORE_EXPORT QWebEngineProfileBuilder &setPersistentPermissionsPolicy(
QWebEngineProfile::PersistentPermissionsPolicy persistentPermissionPolicy);
+ Q_WEBENGINECORE_EXPORT QWebEngineProfileBuilder &
+ setAdditionalTrustedCertificates(const QList<QSslCertificate> &additionalTrustedCertificates);
private:
Q_DISABLE_COPY_MOVE(QWebEngineProfileBuilder)
diff --git a/src/core/api/qwebengineprofilebuilder_p.h b/src/core/api/qwebengineprofilebuilder_p.h
index 4146b5508..a0750d47a 100644
--- a/src/core/api/qwebengineprofilebuilder_p.h
+++ b/src/core/api/qwebengineprofilebuilder_p.h
@@ -30,6 +30,7 @@ struct QWebEngineProfileBuilderPrivate
int m_httpCacheMaxSize;
QWebEngineProfile::PersistentPermissionsPolicy m_persistentPermissionPolicy =
QWebEngineProfile::PersistentPermissionsPolicy::StoreOnDisk;
+ QList<QSslCertificate> m_additionalTrustedCertificates;
};
QT_END_NAMESPACE
#endif // QWEBENGINEPROFILEBUILDER_P_H
diff --git a/src/core/profile_adapter.cpp b/src/core/profile_adapter.cpp
index 4e911b297..941cc62a8 100644
--- a/src/core/profile_adapter.cpp
+++ b/src/core/profile_adapter.cpp
@@ -92,7 +92,8 @@ ProfileAdapter::ProfileAdapter(const QString &storageName, const QString &dataPa
const QString &cachePath, HttpCacheType httpCacheType,
PersistentCookiesPolicy persistentCookiesPolicy,
int httpCacheMaximumSize,
- PersistentPermissionsPolicy persistentPermissionPolicy)
+ PersistentPermissionsPolicy persistentPermissionPolicy,
+ const QList<QSslCertificate> &additionalTrustedCertificates)
: m_name(storageName)
, m_offTheRecord(storageName.isEmpty())
, m_dataPath(dataPath.isEmpty() && !m_name.isEmpty() ? buildLocationFromStandardPath(
@@ -106,6 +107,7 @@ ProfileAdapter::ProfileAdapter(const QString &storageName, const QString &dataPa
, m_persistentCookiesPolicy(persistentCookiesPolicy)
, m_persistentPermissionsPolicy(persistentPermissionPolicy)
, m_visitedLinksPolicy(TrackVisitedLinksOnDisk)
+ , m_additionalTrustedCertificates(additionalTrustedCertificates)
, m_clientHintsEnabled(true)
, m_pushServiceEnabled(false)
, m_httpCacheMaxSize(m_name.isEmpty() ? 0 : httpCacheMaximumSize)
@@ -917,6 +919,11 @@ QWebEngineClientCertificateStore *ProfileAdapter::clientCertificateStore()
}
#endif
+QList<QSslCertificate> ProfileAdapter::additionalTrustedCertificates() const
+{
+ return m_additionalTrustedCertificates;
+}
+
static void callbackOnIconAvailableForPageURL(std::function<void (const QIcon &, const QUrl &, const QUrl &)> iconAvailableCallback,
const QUrl &pageUrl,
const favicon_base::FaviconRawBitmapResult &result)
diff --git a/src/core/profile_adapter.h b/src/core/profile_adapter.h
index cbc811822..75e83cb49 100644
--- a/src/core/profile_adapter.h
+++ b/src/core/profile_adapter.h
@@ -94,7 +94,8 @@ public:
PersistentCookiesPolicy persistentCookiesPolicy = AllowPersistentCookies,
int httpCacheMaximumSize = 0,
PersistentPermissionsPolicy persistentPermissionPolicy =
- PersistentPermissionsPolicy::StoreOnDisk);
+ PersistentPermissionsPolicy::StoreOnDisk,
+ const QList<QSslCertificate> &additionalTrustedCertificates = {});
virtual ~ProfileAdapter();
static ProfileAdapter* createDefaultProfileAdapter();
@@ -205,6 +206,7 @@ public:
#if QT_CONFIG(ssl)
QWebEngineClientCertificateStore *clientCertificateStore();
#endif
+ QList<QSslCertificate> additionalTrustedCertificates() const;
QHash<QByteArray, QWeakPointer<UserNotificationController>> &ephemeralNotifications()
{ return m_ephemeralNotifications; }
@@ -246,6 +248,7 @@ private:
PersistentCookiesPolicy m_persistentCookiesPolicy;
PersistentPermissionsPolicy m_persistentPermissionsPolicy;
VisitedLinksPolicy m_visitedLinksPolicy;
+ QList<QSslCertificate> m_additionalTrustedCertificates;
QHash<QByteArray, QPointer<QWebEngineUrlSchemeHandler>> m_customUrlSchemeHandlers;
QHash<QByteArray, QWeakPointer<UserNotificationController>> m_ephemeralNotifications;
QHash<QByteArray, QSharedPointer<UserNotificationController>> m_persistentNotifications;
diff --git a/src/core/profile_io_data_qt.cpp b/src/core/profile_io_data_qt.cpp
index d57af0b40..30568a716 100644
--- a/src/core/profile_io_data_qt.cpp
+++ b/src/core/profile_io_data_qt.cpp
@@ -236,6 +236,20 @@ void ProfileIODataQt::ConfigureNetworkContextParams(bool in_memory,
m_profile->GetSharedCorsOriginAccessList()->GetOriginAccessList().CreateCorsOriginAccessPatternsList();
m_proxyConfigMonitor->AddToNetworkContextParams(network_context_params);
+
+ const auto additionalCertificates = m_profileAdapter->additionalTrustedCertificates();
+ if (!additionalCertificates.isEmpty()) {
+ auto additionalVerifiedCertificates = cert_verifier::mojom::AdditionalCertificates::New();
+
+ for (const QSslCertificate &certificate : additionalCertificates) {
+ const QByteArray certificateBytes = certificate.toDer();
+ additionalVerifiedCertificates->trust_anchors.push_back(
+ std::vector<uint8_t>(certificateBytes.begin(), certificateBytes.end()));
+ }
+
+ cert_verifier_creation_params->initial_additional_certificates =
+ std::move(additionalVerifiedCertificates);
+ }
}
// static
diff --git a/src/webenginequick/api/qquickwebengineprofileprototype.cpp b/src/webenginequick/api/qquickwebengineprofileprototype.cpp
index af8df1bb0..e6c4b8e99 100644
--- a/src/webenginequick/api/qquickwebengineprofileprototype.cpp
+++ b/src/webenginequick/api/qquickwebengineprofileprototype.cpp
@@ -241,6 +241,30 @@ void QQuickWebEngineProfilePrototype::setPersistentPermissionsPolicy(
}
/*!
+ \qmlproperty list<string> WebEngineProfilePrototype::additionalTrustedCertificateFiles
+
+ A list of paths of additional trusted certificates in this profile's CA certificate database.
+
+ The certificates are read when the profile is created; any invalid paths or certificate files
+ are discarded. The property only holds the paths which were successfully loaded by this profile.
+ This property expects the certificate files to be PEM-encoded.
+*/
+QStringList QQuickWebEngineProfilePrototype::additionalTrustedCertificateFiles() const
+{
+ return d_ptr->m_additionalTrustedCertificateFiles;
+}
+
+void QQuickWebEngineProfilePrototype::setAdditionalTrustedCertificateFiles(const QStringList &paths)
+{
+ if (d_ptr->m_isComponentComplete) {
+ qmlWarning(this) << QStringLiteral("additionalTrustedCertificateFiles is a write-once "
+ "property, and should not be set again.");
+ return;
+ }
+ d_ptr->m_additionalTrustedCertificateFiles = paths;
+}
+
+/*!
\internal
*/
void QQuickWebEngineProfilePrototype::componentComplete()
@@ -276,6 +300,27 @@ void QQuickWebEngineProfilePrototype::componentComplete()
d_ptr->m_persistentCookiesPolicy = QQuickWebEngineProfile::NoPersistentCookies;
}
+ QList<QSslCertificate> additionalCertificates;
+ for (const auto &certFileName : std::as_const(d_ptr->m_additionalTrustedCertificateFiles)) {
+ QFile certFile(certFileName);
+ if (certFile.open(QIODevice::ReadOnly)) {
+ auto &&certs = QSslCertificate::fromDevice(&certFile, QSsl::Pem);
+ if (certs.empty()) {
+ qmlWarning(this) << certFileName
+ << QStringLiteral(" does not contain any valid PEM SSL "
+ "certs. It will be skipped.");
+ }
+ for (auto &&cert : certs) {
+ if (!cert.isNull()) {
+ additionalCertificates.emplace_back(std::move(cert));
+ }
+ }
+ } else {
+ qmlWarning(this) << certFileName
+ << QStringLiteral(" is not found. It will be skipped.");
+ }
+ }
+
auto profileAdapter = new QtWebEngineCore::ProfileAdapter(
d_ptr->m_storageName, d_ptr->m_persistentStoragePath, d_ptr->m_cachePath,
QtWebEngineCore::ProfileAdapter::HttpCacheType(d_ptr->m_httpCacheType),
@@ -283,7 +328,8 @@ void QQuickWebEngineProfilePrototype::componentComplete()
d_ptr->m_persistentCookiesPolicy),
d_ptr->m_httpCacheMaxSize,
QtWebEngineCore::ProfileAdapter::PersistentPermissionsPolicy(
- d_ptr->m_persistentPermissionsPolicy));
+ d_ptr->m_persistentPermissionsPolicy),
+ additionalCertificates);
d_ptr->profile.reset(new QQuickWebEngineProfile(
new QQuickWebEngineProfilePrivate(profileAdapter), this->parent()));
diff --git a/src/webenginequick/api/qquickwebengineprofileprototype_p.h b/src/webenginequick/api/qquickwebengineprofileprototype_p.h
index a29956e5b..ef09eea0d 100644
--- a/src/webenginequick/api/qquickwebengineprofileprototype_p.h
+++ b/src/webenginequick/api/qquickwebengineprofileprototype_p.h
@@ -15,10 +15,14 @@
//
#include <QtWebEngineQuick/qtwebenginequickglobal.h>
-#include <QObject>
#include <QtQml/qqmlregistration.h>
#include <QtQml/qqmlparserstatus.h>
#include <QtWebEngineQuick/QQuickWebEngineProfile>
+#include <QObject>
+#include <QString>
+#include <QList>
+
+#include <memory>
QT_BEGIN_NAMESPACE
@@ -40,6 +44,8 @@ class Q_WEBENGINEQUICK_EXPORT QQuickWebEngineProfilePrototype : public QObject,
Q_PROPERTY(int httpCacheMaximumSize READ httpCacheMaximumSize WRITE setHttpCacheMaximumSize FINAL)
Q_PROPERTY(QQuickWebEngineProfile::PersistentPermissionsPolicy persistentPermissionsPolicy READ
persistentPermissionsPolicy WRITE setPersistentPermissionsPolicy FINAL)
+ Q_PROPERTY(QStringList additionalTrustedCertificateFiles READ additionalTrustedCertificateFiles
+ WRITE setAdditionalTrustedCertificateFiles FINAL)
QML_NAMED_ELEMENT(WebEngineProfilePrototype)
QML_ADDED_IN_VERSION(6, 9)
@@ -70,6 +76,9 @@ public:
void setPersistentPermissionsPolicy(
QQuickWebEngineProfile::PersistentPermissionsPolicy persistentPermissionsPolicy);
+ Q_REVISION(6, 10) QStringList additionalTrustedCertificateFiles() const;
+ Q_REVISION(6, 10) void setAdditionalTrustedCertificateFiles(const QStringList &paths);
+
Q_INVOKABLE QQuickWebEngineProfile *instance();
protected:
diff --git a/src/webenginequick/api/qquickwebengineprofileprototype_p_p.h b/src/webenginequick/api/qquickwebengineprofileprototype_p_p.h
index ddd7e37a9..837f1697c 100644
--- a/src/webenginequick/api/qquickwebengineprofileprototype_p_p.h
+++ b/src/webenginequick/api/qquickwebengineprofileprototype_p_p.h
@@ -5,6 +5,8 @@
#define QQUICKWEBENGINEPROFILEPROTOTYPE_P_P_H
#include "qquickwebengineprofile_p.h"
+
+#include <QScopedPointer>
//
// W A R N I N G
// -------------
@@ -23,6 +25,7 @@ struct QQuickWebEngineProfilePrototypePrivate
QString m_storageName;
QString m_persistentStoragePath;
QString m_cachePath;
+ QStringList m_additionalTrustedCertificateFiles;
QQuickWebEngineProfile::HttpCacheType m_httpCacheType = QQuickWebEngineProfile::DiskHttpCache;
QQuickWebEngineProfile::PersistentCookiesPolicy m_persistentCookiesPolicy =
QQuickWebEngineProfile::AllowPersistentCookies;
diff --git a/tests/auto/quick/publicapi/tst_publicapi.cpp b/tests/auto/quick/publicapi/tst_publicapi.cpp
index 5091caf88..71844c23d 100644
--- a/tests/auto/quick/publicapi/tst_publicapi.cpp
+++ b/tests/auto/quick/publicapi/tst_publicapi.cpp
@@ -974,6 +974,7 @@ static const QStringList expectedAPI = QStringList()
<< "QQuickWebEngineProfilePrototype.persistentCookiesPolicy --> QQuickWebEngineProfile::PersistentCookiesPolicy"
<< "QQuickWebEngineProfilePrototype.httpCacheMaximumSize --> int"
<< "QQuickWebEngineProfilePrototype.persistentPermissionsPolicy --> QQuickWebEngineProfile::PersistentPermissionsPolicy"
+ << "QQuickWebEngineProfilePrototype.additionalTrustedCertificateFiles --> QStringList"
<< "QQuickWebEngineProfilePrototype.instance() --> QQuickWebEngineProfile*"
;
diff --git a/tests/auto/widgets/qwebengineprofilebuilder/CMakeLists.txt b/tests/auto/widgets/qwebengineprofilebuilder/CMakeLists.txt
index d544f3e8b..6eb28cc01 100644
--- a/tests/auto/widgets/qwebengineprofilebuilder/CMakeLists.txt
+++ b/tests/auto/widgets/qwebengineprofilebuilder/CMakeLists.txt
@@ -1,11 +1,24 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
+include(../../httpserver/httpserver.cmake)
include(../../util/util.cmake)
qt_internal_add_test(tst_qwebengineprofilebuilder
SOURCES
tst_qwebengineprofilebuilder.cpp
LIBRARIES
+ Qt::TestPrivate
Qt::WebEngineWidgets
+ Test::HttpServer
+ Test::Util
+)
+
+qt_internal_add_resource(tst_qwebengineprofilebuilder "profilebuilder"
+ PREFIX
+ "/"
+ FILES
+ "resources/server.key"
+ "resources/server.pem"
+ "resources/ca.pem"
)
diff --git a/tests/auto/widgets/qwebengineprofilebuilder/resources/ca.pem b/tests/auto/widgets/qwebengineprofilebuilder/resources/ca.pem
new file mode 100644
index 000000000..c98129042
--- /dev/null
+++ b/tests/auto/widgets/qwebengineprofilebuilder/resources/ca.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUNmY7+SAty0eb0kNneG4LpotXUU0wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl
+cmxpbjEXMBUGA1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5n
+aW5lMRIwEAYDVQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5n
+aW5lQHF0LmlvMB4XDTI1MDUxOTEzMjYwMFoXDTM1MDUxNzEzMjYwMFowgZQxCzAJ
+BgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEXMBUG
+A1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5naW5lMRIwEAYD
+VQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5naW5lQHF0Lmlv
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhsWnavDtnG5/ZXGLWBEu
+xmeu716eWlurgG9zfZBPIgKHYw4pn+sA1HYtpgkoPf+EBBHdtI2ZI4nQpC7Yqmd1
+McYEJoMZJ0QmGeEFXzaEgrcQxFTT8SF6QO6H1h8d6KMywqBRX9pyiZv6dw3zUCWF
+YRGDWLBHTC2LhBQp3AGWm5fEi2PhyIciupaWkTwdwhuDJCJ24DU2K3XbGRL8B+ZF
+6wO0Gdhdr8FG9Gj9PsgYJCiMkJhBwa6alv5RhTKPJ0untyCpm27v/q6HyAYRzve0
+6KIr/CPNJFYj4NUTl4oMg7VZTsYYJz8tIGKEFbR0gLjKbxq1ztNGSZ1CczkavfjI
+awIDAQABo1MwUTAdBgNVHQ4EFgQUSBHNthn7rC8OkfpgK4NNI6eb9sMwHwYDVR0j
+BBgwFoAUSBHNthn7rC8OkfpgK4NNI6eb9sMwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAdrmhBs49NNt8NwdhwDuDY4VrMo8TjsL9KLmk8eP7+n5j
+BW8GzM214M4Nqo5REdEbreU2MmOwla0N3akxoILFTd46Vey+SuZd86qrBejyBT30
+Hqecm7TaceFK/VjOm7N3q7WsZmxt1VQv2h/4qsRo3X0gsaG/Pa30wd95EL5Iq8UC
+noBcwSSawlyMVmb3QiGzD0h+ONlIn+AcSKjgybyoXZ3ga7fRDNuWRmSYTwgRnMWt
+D20jt9BPkOMhajCoN1tQrtKvB5s8nZTwFYJ61OnqQhOpaPSdk2mY+0Ty9xN8815D
+tejXU/6DERrGxJLrqkix/ORg3bP2kCEVMMvmJqrUCg==
+-----END CERTIFICATE-----
diff --git a/tests/auto/widgets/qwebengineprofilebuilder/resources/server.key b/tests/auto/widgets/qwebengineprofilebuilder/resources/server.key
new file mode 100644
index 000000000..593e7f174
--- /dev/null
+++ b/tests/auto/widgets/qwebengineprofilebuilder/resources/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCeqmuz7jA7uSu1
+t2qP+irhMeqHvkkFP3PRFPKoP7lThds+j8Y6mGhm3Wii01ujK7y8imoTm4am+IhF
+FT/xPs+EyKHva/TT8gjbVtR1m4jKEikQ7AEIZXMwhmR3J99zadRtJZO6qhCC7ER3
+P2BStEtxBibmZQqw0OZNsZZrIXjRX1s68j5lCA994RB1VQOIFnRgf6ZXLD1MwNtS
+9VeUwA6H/kwCCSnAO2eJgK9OgkhU+f3kY8j1tcQiwHpTBinLgbQvgy0nJJwxrV1e
+HZE5sVNuLdlS7szln/juLJ8IF3UMDvqLqDl/L7Rk+nAn4bT/P6b0IVAWyyfXQdxT
+Rml6qIgzAgMBAAECggEADUtOipJxXyevkQ0PtDBBXyyyr8dSpfUVlqtbKPFsl/d2
+Ec1eeMgOrgPCLIEUw1y69nngfnPAZLAb9tyGHDK86i1yCAtdxakYyIwDcYvjz7lA
+JfH9pNRJ7QMYWuAv5E3gkHlJYfwK4HVnHS4QZjhDaUfF2EsXX2OkJawKDurh4npU
+ICRwMG20vnAphYCw0CRbnxEBissLXpSyMDmt174jSG6houqQF8JNQUem/TG20o9X
+uhnlRvKKTeL3SN83gIx5BdHFL6uwG7qt6lUeINBjA8i0BIB37i2bu/8rOMggQFws
+G80aCdQ62vp54BcCGxBnjmljp+inU3s7quvTKOBlnQKBgQDe5UQdiWcteZlWRL+6
+b6LQ5X65UQeLTv/mi1mEfGg1UvLv2kTTu85/8Lkts1DAGj5Rb34ckGf466Y6gUkT
+c+zgucYs3n9SaboPA2fEknDJtrjdcL0xUDnPsdtgLLTmxdvaiW7uYxf36/YGFDNl
+kyG92ZC+cATSIxgiHXYg6OXQ9QKBgQC2OxCVMoib5J0jO7Uus17xQyOFV4vR07rv
+90i+diLq629TiT+ff82iKOcSPMcg1AtLquymGf1OwAmDK3+L6fpmjobR/BnXcNer
+RKJGbca8GvE+fpUTTDoGAlGD23w99LAHtoSjqxSOtIU5souLsPeBXdckJlQ+bxWe
+iqe6UEebhwKBgA3nTzBoeb8kbqQq9aqze/x71EPLAiV2cA/5cUQKXpW07uJ3QwPS
+Gzdv1J09KjRRbsG1qrAtcc7dJClSFzTXblc2P15dIqQJZEm7dKWWXOK4Ox/VAHgr
+APArr/t3znD2tpgTKpBELiKQ3W/TosEbRGeLQrQeWK6i8cZvAAddf7hhAoGAC0+3
+Q8uTNzoFlv5JzNBNgGROfCRnBWtDG0oaNdhXaoWar3DBhkEEnqAzV6p2Ic+Hs/a0
+IctTMeQxsvasQB8R7/PA4p/narwSZwsnl3+Q6nQxrVNmJYCByYWzUZ/6Ik5h7tih
+exdPe1wxONegWdduRZVxmUjXydhTWzf4GVSKXVkCgYEAqxCADKNL+TZrHqOcyENv
+vR25mUbAV8QiTX5+e9bO3STTZLZp0TWv/UxSi9Fd9tV4TijrA3ZYbJcjEuoVMNy8
+vUillTR0ZpSD2WpM1aAliR9Lh55/sTRVATgD73TUcTwpD3H887WSfPrV3oPE901i
+oSCfd2ZHwGsyCc1iYLuiC6Y=
+-----END PRIVATE KEY-----
diff --git a/tests/auto/widgets/qwebengineprofilebuilder/resources/server.pem b/tests/auto/widgets/qwebengineprofilebuilder/resources/server.pem
new file mode 100644
index 000000000..c384f8c74
--- /dev/null
+++ b/tests/auto/widgets/qwebengineprofilebuilder/resources/server.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKDCCAxCgAwIBAgIUEcR3S839Xdw1qu4MgJi7YnhIDFEwDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl
+cmxpbjEXMBUGA1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5n
+aW5lMRIwEAYDVQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5n
+aW5lQHF0LmlvMB4XDTI1MDUxOTEzMzYwOVoXDTM1MDUxNzEzMzYwOVowgZQxCzAJ
+BgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEXMBUG
+A1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5naW5lMRIwEAYD
+VQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5naW5lQHF0Lmlv
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnqprs+4wO7krtbdqj/oq
+4THqh75JBT9z0RTyqD+5U4XbPo/GOphoZt1ootNboyu8vIpqE5uGpviIRRU/8T7P
+hMih72v00/II21bUdZuIyhIpEOwBCGVzMIZkdyffc2nUbSWTuqoQguxEdz9gUrRL
+cQYm5mUKsNDmTbGWayF40V9bOvI+ZQgPfeEQdVUDiBZ0YH+mVyw9TMDbUvVXlMAO
+h/5MAgkpwDtniYCvToJIVPn95GPI9bXEIsB6UwYpy4G0L4MtJyScMa1dXh2RObFT
+bi3ZUu7M5Z/47iyfCBd1DA76i6g5fy+0ZPpwJ+G0/z+m9CFQFssn10HcU0ZpeqiI
+MwIDAQABo3AwbjAsBgNVHREEJTAjhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABggls
+b2NhbGhvc3QwHQYDVR0OBBYEFESEhXY1rLWAZWAHyCoRCuUxsJPdMB8GA1UdIwQY
+MBaAFEgRzbYZ+6wvDpH6YCuDTSOnm/bDMA0GCSqGSIb3DQEBCwUAA4IBAQABJIpy
+SUgxsjnt0uVvsGUbXcd2tC9vYlO8B9IILZOXQ8Z47v8zq/Rly4sL9uP0eVquYEua
+/KTaJNVNzpBlrAn9GQyhZKggcaMpaUxrLsEgkBV1GDO3N93K7T/DzE8650nwrhpB
+m3SxUDvWtB5xFfGuCOv2M0oAjievnRPDvOvDq8kWJ5Oi3DyfIhP68WNS+xQdlWF2
+V+zNnhrkCyRzuTzf2BC2mofemxhx4oa3buEU/I7n/AXivvTsANHNs1GlpY7cq88p
+xeqStwaqw5+agA3tgbP0vFXorfrZnRXaim/PfbjurjGcoersQnmdJ8yEWL0y6LjS
+A0TByY327Db5AS52
+-----END CERTIFICATE-----
diff --git a/tests/auto/widgets/qwebengineprofilebuilder/tst_qwebengineprofilebuilder.cpp b/tests/auto/widgets/qwebengineprofilebuilder/tst_qwebengineprofilebuilder.cpp
index 43b754ad9..c0c087f5c 100644
--- a/tests/auto/widgets/qwebengineprofilebuilder/tst_qwebengineprofilebuilder.cpp
+++ b/tests/auto/widgets/qwebengineprofilebuilder/tst_qwebengineprofilebuilder.cpp
@@ -1,9 +1,15 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#include <httpsserver.h>
+#include <util.h>
#include <QtTest/QtTest>
+#include <QtTest/private/qtesthelpers_p.h>
#include <QTemporaryDir>
#include <QtWebEngineCore/qwebengineprofilebuilder.h>
+#include <QtWebEngineCore/qwebenginecertificateerror.h>
+#include <QtWebEngineCore/qwebengineclientcertificatestore.h>
+#include <QtWebEngineCore/qwebenginesettings.h>
class tst_QWebEngineProfileBuilder : public QObject
{
@@ -21,6 +27,7 @@ private Q_SLOTS:
void httpCacheSize();
void persistentPermissionsPolicy_data();
void persistentPermissionsPolicy();
+ void additionalTrustedCertificates();
void useSameDataPathForProfiles();
};
@@ -275,6 +282,97 @@ void tst_QWebEngineProfileBuilder::persistentPermissionsPolicy()
QCOMPARE(profile->persistentStoragePath(), storagePath);
}
+void tst_QWebEngineProfileBuilder::additionalTrustedCertificates()
+{
+ if (QTestPrivate::isSecureTransportBlockingTest()) {
+ QSKIP("SecureTransport will block the test server while accessing the login keychain");
+ }
+
+ QFile certFile(":/resources/server.pem");
+ QVERIFY2(certFile.open(QIODevice::ReadOnly), qPrintable(certFile.errorString()));
+ const QSslCertificate cert(&certFile, QSsl::Pem);
+
+ QFile keyFile(":/resources/server.key");
+ QVERIFY2(keyFile.open(QIODevice::ReadOnly), qPrintable(keyFile.errorString()));
+ const QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, "");
+
+ HttpsServer server(":/resources/server.pem", ":/resources/server.key", ":/resources/ca.pem");
+ server.setExpectError(false);
+ QVERIFY(server.start());
+
+ connect(&server, &HttpsServer::newRequest, [](HttpReqRep *rr) {
+ rr->setResponseBody(QByteArrayLiteral("<html><body>TEST</body></html>"));
+ rr->sendResponse();
+ });
+
+ {
+ QWebEnginePage page;
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ page.profile()->clientCertificateStore()->add(cert, sslKey);
+
+ connect(&page, &QWebEnginePage::selectClientCertificate, &page,
+ [](QWebEngineClientCertificateSelection selection) {
+ Q_UNUSED(selection)
+ QFAIL("Should have rejected handshake already.");
+ });
+
+ QSignalSpy certificateErrorSpy(&page, &QWebEnginePage::certificateError);
+ page.setUrl(server.url());
+
+ QTRY_COMPARE_WITH_TIMEOUT(certificateErrorSpy.size() > 0, true, 20000);
+
+ auto error = certificateErrorSpy.takeFirst().at(0).value<QWebEngineCertificateError>();
+ QCOMPARE(error.type(), QWebEngineCertificateError::CertificateAuthorityInvalid);
+ }
+
+ // Add the appropriate server certificate, connection should work then.
+
+ QList<QSslCertificate> certs;
+
+ for (QString filename : {":/resources/server.pem", ":/resources/ca.pem"}) {
+ QFile file(filename);
+ QVERIFY2(file.open(QIODevice::ReadOnly), qPrintable(file.errorString()));
+ certs.emplace_back(&file, QSsl::Pem);
+ QVERIFY(!certs.back().isNull());
+ }
+
+ QWebEngineProfileBuilder profileBuilder;
+ profileBuilder.setAdditionalTrustedCertificates(certs);
+ QScopedPointer<QWebEngineProfile> profile(profileBuilder.createProfile(QStringLiteral("Test")));
+ QVERIFY(profile);
+
+ QCOMPARE(profile->additionalTrustedCertificates(), certs);
+
+ {
+ QWebEnginePage page(profile.get());
+ page.settings()->setAttribute(QWebEngineSettings::ErrorPageEnabled, false);
+
+ page.profile()->clientCertificateStore()->add(cert, sslKey);
+
+ connect(&page, &QWebEnginePage::selectClientCertificate, &page,
+ [&cert](QWebEngineClientCertificateSelection selection) {
+ QVERIFY(!selection.certificates().isEmpty());
+ for (const QSslCertificate &sCert : selection.certificates()) {
+ if (cert == sCert) {
+ selection.select(sCert);
+ return;
+ }
+ }
+ QFAIL("No certificate found.");
+ });
+
+ QSignalSpy loadFinishedSpy(&page, &QWebEnginePage::loadFinished);
+ page.setUrl(server.url());
+
+ QTRY_COMPARE_WITH_TIMEOUT(loadFinishedSpy.size() > 0, true, 20000);
+ QCOMPARE(loadFinishedSpy.takeFirst().at(0).toBool(), true);
+ QCOMPARE(toPlainTextSync(&page), QStringLiteral("TEST"));
+ }
+
+ QVERIFY(server.stop());
+}
+
void tst_QWebEngineProfileBuilder::useSameDataPathForProfiles()
{
QWebEngineProfileBuilder profileBuilder;