diff options
author | Kai Uwe Broulik <[email protected]> | 2025-05-20 08:29:58 +0200 |
---|---|---|
committer | Kai Uwe Broulik <[email protected]> | 2025-05-27 09:16:17 +0200 |
commit | 671afae06e9f9d228e9563c7c60a31456d2b2741 (patch) | |
tree | 700fc0846fb05b93bc126ddf20962cd7211140c8 | |
parent | 5c6200c971b5478c08023f4233f248f70e09e3d7 (diff) |
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]>
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; |