From e6a13d97898f3f4b19f61006d0716ca9e1b9f037 Mon Sep 17 00:00:00 2001 From: Michal Klocek Date: Wed, 16 Nov 2022 11:05:52 +0100 Subject: Add client certificate example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-106497 Pick-to: 6.4 Change-Id: I39e39a991362940bb35052d28254b7b12acaa105 Reviewed-by: Michael BrĂ¼ning --- examples/webenginewidgets/CMakeLists.txt | 3 + .../clientcertificate/CMakeLists.txt | 60 ++++++++ .../webenginewidgets/clientcertificate/client.cpp | 67 +++++++++ .../webenginewidgets/clientcertificate/client.pro | 10 ++ .../clientcertificate/clientcertificate.pro | 7 + .../clientcertificate/doc/images/granted.png | Bin 0 -> 5031 bytes .../clientcertificate/doc/images/selection.png | Bin 0 -> 6300 bytes .../doc/src/clientcertificate.qdoc | 159 +++++++++++++++++++++ .../clientcertificate/resources/ca.pem | 24 ++++ .../clientcertificate/resources/client.key | 27 ++++ .../clientcertificate/resources/client.pem | 22 +++ .../clientcertificate/resources/client.qrc | 6 + .../clientcertificate/resources/server.key | 27 ++++ .../clientcertificate/resources/server.pem | 22 +++ .../clientcertificate/resources/server.qrc | 7 + .../webenginewidgets/clientcertificate/server.cpp | 99 +++++++++++++ .../webenginewidgets/clientcertificate/server.pro | 11 ++ examples/webenginewidgets/webenginewidgets.pro | 3 +- src/core/doc/src/qtwebengine-features.qdoc | 3 + 19 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 examples/webenginewidgets/clientcertificate/CMakeLists.txt create mode 100644 examples/webenginewidgets/clientcertificate/client.cpp create mode 100644 examples/webenginewidgets/clientcertificate/client.pro create mode 100644 examples/webenginewidgets/clientcertificate/clientcertificate.pro create mode 100644 examples/webenginewidgets/clientcertificate/doc/images/granted.png create mode 100644 examples/webenginewidgets/clientcertificate/doc/images/selection.png create mode 100644 examples/webenginewidgets/clientcertificate/doc/src/clientcertificate.qdoc create mode 100644 examples/webenginewidgets/clientcertificate/resources/ca.pem create mode 100644 examples/webenginewidgets/clientcertificate/resources/client.key create mode 100644 examples/webenginewidgets/clientcertificate/resources/client.pem create mode 100644 examples/webenginewidgets/clientcertificate/resources/client.qrc create mode 100644 examples/webenginewidgets/clientcertificate/resources/server.key create mode 100644 examples/webenginewidgets/clientcertificate/resources/server.pem create mode 100644 examples/webenginewidgets/clientcertificate/resources/server.qrc create mode 100644 examples/webenginewidgets/clientcertificate/server.cpp create mode 100644 examples/webenginewidgets/clientcertificate/server.pro diff --git a/examples/webenginewidgets/CMakeLists.txt b/examples/webenginewidgets/CMakeLists.txt index 5b0a56d23..c94f71fb4 100644 --- a/examples/webenginewidgets/CMakeLists.txt +++ b/examples/webenginewidgets/CMakeLists.txt @@ -24,3 +24,6 @@ if(QT_FEATURE_webengine_spellchecker AND NOT CMAKE_CROSSCOMPILING AND NOT QT_FEATURE_webengine_native_spellchecker AND NOT WIN32) qt_internal_add_example(spellchecker) endif() +if(QT_FEATURE_ssl) + qt_internal_add_example(clientcertificate) +endif() diff --git a/examples/webenginewidgets/clientcertificate/CMakeLists.txt b/examples/webenginewidgets/clientcertificate/CMakeLists.txt new file mode 100644 index 000000000..08a4951f1 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/CMakeLists.txt @@ -0,0 +1,60 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(maps LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) + +if(NOT DEFINED INSTALL_EXAMPLESDIR) + set(INSTALL_EXAMPLESDIR "examples") +endif() + +set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/webenginewidgets/clientcertificate") + +find_package(Qt6 REQUIRED COMPONENTS Core Gui WebEngineWidgets) + +qt_add_executable(server + server.cpp +) + +qt_add_executable(client + client.cpp +) + +set_target_properties(client PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +qt_internal_add_resource(client "client" + PREFIX + "/" + FILES + "resources/client.pem" + "resources/client.key" +) + +qt_internal_add_resource(server "server" + PREFIX + "/" + FILES + "resources/server.pem" + "resources/server.key" + "resources/ca.pem" +) + +target_link_libraries(client PUBLIC + Qt::WebEngineWidgets +) + +target_link_libraries(server PUBLIC + Qt::Core + Qt::Network +) + +install(TARGETS server client + RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}" + BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}" + LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}" +) diff --git a/examples/webenginewidgets/clientcertificate/client.cpp b/examples/webenginewidgets/clientcertificate/client.cpp new file mode 100644 index 000000000..1227fa28e --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/client.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("QtExamples"); + QApplication app(argc, argv); + + QFile certFile(":/resources/client.pem"); + certFile.open(QIODevice::ReadOnly); + const QSslCertificate cert(certFile.readAll(), QSsl::Pem); + + QFile keyFile(":/resources/client.key"); + keyFile.open(QIODevice::ReadOnly); + const QSslKey sslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, ""); + + QWebEngineProfile::defaultProfile()->clientCertificateStore()->add(cert, sslKey); + + QWebEnginePage page; + QObject::connect(&page, &QWebEnginePage::certificateError, + [](QWebEngineCertificateError e) { e.acceptCertificate(); }); + + QObject::connect( + &page, &QWebEnginePage::selectClientCertificate, &page, + [&cert](QWebEngineClientCertificateSelection selection) { + QDialog dialog; + QVBoxLayout *layout = new QVBoxLayout; + QLabel *label = new QLabel(QLatin1String("Select certificate")); + QListWidget *listWidget = new QListWidget; + listWidget->setSelectionMode(QAbstractItemView::SingleSelection); + QPushButton *button = new QPushButton(QLatin1String("Select")); + layout->addWidget(label); + layout->addWidget(listWidget); + layout->addWidget(button); + QObject::connect(button, &QPushButton::clicked, [&dialog]() { dialog.accept(); }); + const QList &list = selection.certificates(); + for (const QSslCertificate &cert : list) { + listWidget->addItem(cert.subjectDisplayName() + " : " + cert.serialNumber()); + } + dialog.setLayout(layout); + if (dialog.exec() == QDialog::Accepted) + selection.select(list[listWidget->currentRow()]); + else + selection.selectNone(); + }); + + QWebEngineView view(&page); + view.setUrl(QUrl("https://localhost:5555")); + view.resize(800, 600); + view.show(); + + return app.exec(); +} diff --git a/examples/webenginewidgets/clientcertificate/client.pro b/examples/webenginewidgets/clientcertificate/client.pro new file mode 100644 index 000000000..e397d5efa --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/client.pro @@ -0,0 +1,10 @@ +TEMPLATE = app + +QT += webenginewidgets + +SOURCES += client.cpp + +RESOURCES += resources/client.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/clientcertificate/client +INSTALLS += target diff --git a/examples/webenginewidgets/clientcertificate/clientcertificate.pro b/examples/webenginewidgets/clientcertificate/clientcertificate.pro new file mode 100644 index 000000000..66039d05c --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/clientcertificate.pro @@ -0,0 +1,7 @@ +QT_FOR_CONFIG += network-private +TEMPLATE = subdirs + +client.file = client.pro +server.file = server.pro + +qtConfig(ssl): SUBDIRS += client server diff --git a/examples/webenginewidgets/clientcertificate/doc/images/granted.png b/examples/webenginewidgets/clientcertificate/doc/images/granted.png new file mode 100644 index 000000000..b2def9b5e Binary files /dev/null and b/examples/webenginewidgets/clientcertificate/doc/images/granted.png differ diff --git a/examples/webenginewidgets/clientcertificate/doc/images/selection.png b/examples/webenginewidgets/clientcertificate/doc/images/selection.png new file mode 100644 index 000000000..2756ac7be Binary files /dev/null and b/examples/webenginewidgets/clientcertificate/doc/images/selection.png differ diff --git a/examples/webenginewidgets/clientcertificate/doc/src/clientcertificate.qdoc b/examples/webenginewidgets/clientcertificate/doc/src/clientcertificate.qdoc new file mode 100644 index 000000000..5f4eeb848 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/doc/src/clientcertificate.qdoc @@ -0,0 +1,159 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \example webenginewidgets/clientcertificatesi + \title WebEngine Widgets Client Certificate Example + \ingroup webengine-widgetexamples + \brief A simple client certificate authentication scenario using \QWE and \l QSslServer + + \image selection.png + + In this example we are going to show a client certificate authentication workflow. + The presented authentication scenario can be for example implemented + for an embedded device, which provides a web interface to handle its functionality. + The administrator uses the \QWE powered client to maintain the embedded device + and has a custom SSL certificate to authenticate. + The connection is encrypted with SSL sockets. The embedded device uses + a \c QSslSocket to handle the authentication and the encryption. This way the + administrator does not have to enter any credentials and just needs to select + a proper certificate that is recognized by the device. + + In the example we focus on a very simple and minimalistic approach to demonstrate + the workflow. Note that QSslSocket is a low level solution as we do not have to + run a full-blown HTTPS server on the resource limited embedded device. + + \section1 Creating Certificates + + The example comes with certificates already generated, but let's see how to generate + new ones. We create certificates for the server and the client using + \l{https://www.openssl.org}{OpenSSL tooling}. + + First, we create the certificate signing request \c CSR and sign it. We will use + a CA private key to sign and issue both local certificates for the client and the server. + + \badcode + openssl req -out ca.pem -new -x509 -nodes -keyout ca.key + \endcode + + \note Specify the \c {-days} option to override the default certificate validity of 30 days. + + Now, let's create two private keys for our client and a server: + + \badcode + openssl genrsa -out client.key 2048 + \endcode + \badcode + openssl genrsa -out server.key 2048 + \endcode + + Next we need two certificate signing requests: + + \badcode + openssl req -key client.key -new -out client.req + \endcode + \badcode + openssl req -key server.key -new -out server.req + \endcode + + Let's issue now both certificates from CSRs: + + \badcode + openssl x509 -req -in client.req -out client.pem -CA ca.pem -CAkey ca.key + \endcode + \badcode + openssl x509 -req -in server.req -out server.pem -CA ca.pem -CAkey ca.key + \endcode + + The client certificate subject and the serial number will be displayed for + selection during authentication. The serial number can be printed with: + + \badcode + openssl x509 -serial -noout -in client.pem + \endcode + + \section1 Implementing the Client + + Now we can implement our web browser client. + + We start by loading our certificate and its private key and creating \l QSslCertificate + and \l QSslKey instances. + + + \quotefromfile webenginewidgets/clientcertificate/client.cpp + \skipto QFile + \printuntil QSslKey + + Now we add the certificate and its private key to \l {QWebEngigneCretificateStore}. + + \printuntil clientCertificateStore + + To handle certificates we need to create an instance of \l QWebEnginePage and connect to two + singals \l QWebEnginePage::certificateError and \l QWebEnginePage::selectClientCertificate. + The first one is only needed as our self-signed server certificate will trigger a certificate + error, which has to be accepted to proceed with the authentication. In production + environments self-signed certificates are not used, therefore in this example we handle + \l QWebEngineCertificateError just to avoid providing proper certificates. + Note the private key is a secret and should never be published. + + \printuntil acceptCertificate + + The handling for \l QWebEnginePage::selectClientCertificate simply displays \l QDialog + with \l QWidgetList showing a list of client certificates to choose from. + The user selected certificate is then passed to the + \l QWebEngineClientCertificateSelection::select call. + + \printto QWebEngineView + + Finally, we create a \l QWebEngineView for our \l QWebEnginePage, load the server + URL, and show the page. + + \printuntil show + + \section1 Implementing the Server + + For our embedded device we will develop a minimalistic HTTPS server. We can use \l QSslServer + to handle incoming connections and to provide an \l QSslSocket instance. To do that, + we create an instance of a \l QSslServer and, similarly to our client setup, we load a server + certificate and its private key. Next, we create \l QSslCertifcate and \l QSslKey objects + accordingly. Additionally, we need a CA certificate so the server can validate the certificate + presented by the client. The CA and local certificate are set to \l QSslConfiguration and + used later by the server. + + \quotefromfile webenginewidgets/clientcertificate/server.cpp + \skipto QSslServer + \printuntil setSslConfiguration + + Next, we set the server to listen for incoming connections on port \c 5555 + + \printuntil qInfo + + We provide a lambda function for the \l QTcpServer::pendingConnectionAvailable signal, + where we implement handling for incoming connections. This signal is triggered + after authentication has succeeded and \c socket TLS encryption has started. + + \printto readyRead + + The \c Request object used above is a simple wrapper around \l QByteArray as we use + \l QPointer to help with memory management. This object gathers incoming HTTP data. + It is deleted when the request has completed or a socket has been terminated. + + \quotefromfile webenginewidgets/clientcertificate/server.cpp + \skipto struct + \printuntil }; + + The reply for the request depends on the requested URL, and it is sent back through + the socket in form of a HTML page. For the \c GET root request the administrator sees + the \c {Access Granted} message and an \c {Exit} HTML button. If the administrator clicks it, + the client sends another request. This time with the \c{/exit} relative URL, + which it turn triggers the server termination. + + \quotefromfile webenginewidgets/clientcertificate/server.cpp + \skipto readyRead + \printuntil }); + + To run the example, start the \c server and then the \c client. After you select + the certificate, the \c {Access Granted} page is displayed. + + \image granted.png +*/ diff --git a/examples/webenginewidgets/clientcertificate/resources/ca.pem b/examples/webenginewidgets/clientcertificate/resources/ca.pem new file mode 100644 index 000000000..cb62ad62c --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIECzCCAvOgAwIBAgIUdhDW1WgGxF313LYA0JjEQpKbanQwDQYJKoZIhvcNAQEL +BQAwgZQxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJl +cmxpbjEXMBUGA1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5n +aW5lMRIwEAYDVQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5n +aW5lQHF0LmlvMB4XDTIyMTExNjExMDQxNFoXDTMyMTExMzExMDQxNFowgZQxCzAJ +BgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xDzANBgNVBAcMBkJlcmxpbjEXMBUG +A1UECgwOVGhlIFF0IENvbXBhbnkxFDASBgNVBAsMC1F0V2ViRW5naW5lMRIwEAYD +VQQDDAl3d3cucXQuaW8xIDAeBgkqhkiG9w0BCQEWEXF0d2ViZW5naW5lQHF0Lmlv +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxyNLLwAA+FgNQavVJ19n +gdoy+NKLHQyhzcRFykKSp9aAbpAR6e4ukxwG7mWNBcuR7zv1Zw/JqLFE0gmVztVw +FeQWdw1cvTN/OlVEuM+0ShTDHHsCqRpx7/XJT6ytMKVU8jdZN4Vl1m7MubWv4aPy +0WYYd3zIAicciYgy/RHaRhPTKpPzWIPYhmHsM5w2cebL8I0aZXUkC0OeklJArnp9 +007Fr6SXXK0xQ3RO20n7X193gCfd5U70lug0ks/ZZqxtzPHmzIO1WGAOBura50HR +hxUKAu7qQHzBiW5Qwdn0af4FPLJR/SX8ADKTLCSWlMOo1FLYO5w6D8hB4K6/b9VQ +RwIDAQABo1MwUTAdBgNVHQ4EFgQUXuTuB85/iBgwJpLdOc+8TB0KESIwHwYDVR0j +BBgwFoAUXuTuB85/iBgwJpLdOc+8TB0KESIwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEAvtucUJa0IECltWv8U6R+LQuZ1Q+ubbmstojO/h8tg6Wf +v6FZ5bH3oboSyGEcytRr6INf4G6znUNAypbodehAEW6/PETdzGM9CJyv2JPJAWzV +rxb1H5VTyiEs8924QOqcNATD+oe7G0vwnDkvprcqaWBA6yvQkWpCXoqMc+F95KnY +8VFt2VQw17l4L4nhaX3Us6hJLMiKV+dLeF0pN+pkCPRP9G5WKgW3mT2U6Gig+rLz +6L7rBbb5KWAttdAbuHCrMa65PgXoVD1P/GteFxUnghDd0PWgUaign8c/DyHGsrbA +uvJqSym0kmQQXptryRaKFsGcCrizdbE6FfrH2iE7vQ== +-----END CERTIFICATE----- diff --git a/examples/webenginewidgets/clientcertificate/resources/client.key b/examples/webenginewidgets/clientcertificate/resources/client.key new file mode 100644 index 000000000..21c8e3183 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqnHbq38y1VprEaV2xXzv2nAPyjqCuIfuick8qETkzEsNWPQi +dsBlLfcyf+15wEMhpRIwILXCrUM7Sb7WCGtg1XC00JZvCh2xPBMSD2fiQyHn4men +Fwh9vVbTf1v7w21ZT/pXQrwlgLgNWYZHE3JrcEAwlThQRIdQfzSE6/QeHfYZoGB9 +WfvbREsOWiUlZze/yrblS9vnAVhYwVurelc7lXyHA0dHmkcZ0HwMxVJZ/vLuCyIw +lNGT/ytnA9p1l8uFkAgTcbWZKoyJAsAZG9faZp46hk8+e3KAyKQ78aoUSbjAqnNQ +tBM3bnHeHanf3ddCxyej+k9PfSIY27a9FZxHpQIDAQABAoIBAFsomA8p8ZsQR9Fh +SJupDXMrmhZTotRkxxxkR4/LgP8OaO4ZbFFM5xBldFndPc+pV9Y8WwczjxIxsgTo +Dvrjyx98rwgcXPjxFniFzpP0wJudB7McMs5r2SwpwuYL4SQNWMYgowjrLbehOGqY +GW16NaIMgq9cNfng0RmnkivMHUtyE5GGdK+C6cyK+fIE+cNtQtHPRKfEnwbE9VHz +3EY/nCXGZvMFyj5uHaU4EeZFCzo19TUqhh8H7b0EA44pBtb5U/CxsH4xphZ7rpjt +iVjMfRSMR4qalQNIs6ZEj57We+M/zca/Qq1yhjW+0NYbZifcYo1Oj6e4lC9YlIgn +kGkcuUECgYEA1j0iVFjgBXS8pJP3jBgmbrbBBTNEUv27yjnJCAQx5TbplJkvBM4/ +qzum1uH2o6uRrFtrYJFiAhDHARtg+70rMeYqZp8WFvzJT5c5s+FOmGQPfFjgrD6e +wfnCwFzS7nohJ8TM2mPGJ88pBv0eBYW6D0f7fvcJmEk8hnGktdLRCrECgYEAy6tU +YFZDzGhbgrG2wWzBvAKVngUNhrYZHMiF1WVN8zZdCm7Z8b1S/NMe0rPA5orhAkSX +8fxlDfKOm+U2fKp43aiN0NDiP0TlGRbypAXe7FSnvDxNHbV+Ie0UbwuiJ4s3vJuc +6cdzgKqAs5/rjPXPdUpM8C7344HV7azgSzHIYTUCgYAtVmCmcuxtmye0uG+BoTa4 +5UnxvMivu2x7PkFRxfl9JWLHBKfTn4YPyZ7kCIu2VT+NtwcBN6MDBuPmUxHyFDVI +6Ql+EBqPoM1FX55hd8O3Mi2oxfI94T6dlCpnpP0qZIQRs28apFSx5gArr3Mj/gnC +5BvP4Z2RMaZyWShfJg8A8QKBgQClZEhswyDjiYtmorJqeMsKxn6BiFDnqFDUUvJ7 +zHx0mR0NL9/Es54Eud059ccccIMwuEs7s17M6MBuUMDik/z647nmbPqNroDs0vnP +wQS6njRoY/+rtIrtOf1x/9x6iE+G1keigNmHDu7c72z1V1hVQzUfhsS+99yl2dF6 +vr6eUQKBgF/OHW1bE3FruZ+53Arcb94N/IKnpH9VWoB3elIzr0w6pLtL4HHhmQ58 +TayEpq6YguUAjTvCBbaHuYuKPHiXCAy5DhtrXvP4YdMNH9X1nHc7jVEbGltVbnQU +bG/p5YfZSrDmsjf8w0z7feFOcovC6vF1YCXc8OHK/LQ6JFJ/gtO1 +-----END RSA PRIVATE KEY----- diff --git a/examples/webenginewidgets/clientcertificate/resources/client.pem b/examples/webenginewidgets/clientcertificate/resources/client.pem new file mode 100644 index 000000000..dd1f898f7 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/client.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApcCFFNQAgGBu5nr81tUMdXXLGkm8Li+MA0GCSqGSIb3DQEBCwUAMIGU +MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x +FzAVBgNVBAoMDlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTES +MBAGA1UEAwwJd3d3LnF0LmlvMSAwHgYJKoZIhvcNAQkBFhFxdHdlYmVuZ2luZUBx +dC5pbzAeFw0yMjExMTYxMjExMDFaFw0zMjExMTMxMjExMDFaMIGSMQswCQYDVQQG +EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFzAVBgNVBAoM +DlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTEVMBMGA1UEAwwM +Y2xpZW50LnF0LmlvMRswGQYJKoZIhvcNAQkBFgxjbGllbnRAcXQuaW8wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqcdurfzLVWmsRpXbFfO/acA/KOoK4 +h+6JyTyoROTMSw1Y9CJ2wGUt9zJ/7XnAQyGlEjAgtcKtQztJvtYIa2DVcLTQlm8K +HbE8ExIPZ+JDIefiZ6cXCH29VtN/W/vDbVlP+ldCvCWAuA1ZhkcTcmtwQDCVOFBE +h1B/NITr9B4d9hmgYH1Z+9tESw5aJSVnN7/KtuVL2+cBWFjBW6t6VzuVfIcDR0ea +RxnQfAzFUln+8u4LIjCU0ZP/K2cD2nWXy4WQCBNxtZkqjIkCwBkb19pmnjqGTz57 +coDIpDvxqhRJuMCqc1C0Ezducd4dqd/d10LHJ6P6T099Ihjbtr0VnEelAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBALE75ZQxmEXJA16cNAxxmxCKHkaqAE6Ulim1vXNH +jCFfNCDGYn/R28F3BVtMe+bIMoomaTh3h5eOd/9uc2nm8IiT5FUz9epJWPeRG/cl +I+hQ3fvaE7oJ3m3EwfGq1mdqUf1zi+DFjtkimNbn9ZRDocZfpO5VN0u23ptEuk0P +5cH4+Dst0giRMv5W0kXG6QD13H/eVH3jDZCtZa/8T4oxGGskHEa4yDr8s976lVOV +XLI1r7oN4a/KXKow8WN3oHFeKn4QJx86z1uecuZLtT8xjABKSWpZqgsIlmGTGE1a +9W06C+uPVamwn5ND3gnf93YQqn6PwrjlHdrQOTG/vngJLPw= +-----END CERTIFICATE----- diff --git a/examples/webenginewidgets/clientcertificate/resources/client.qrc b/examples/webenginewidgets/clientcertificate/resources/client.qrc new file mode 100644 index 000000000..cc3492e80 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/client.qrc @@ -0,0 +1,6 @@ + + + client.key + client.pem + + diff --git a/examples/webenginewidgets/clientcertificate/resources/server.key b/examples/webenginewidgets/clientcertificate/resources/server.key new file mode 100644 index 000000000..632cc4d2e --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA5gQoJryenjmvzy4RbqHdNXHK8Gk/8Lto1SwT8+Wbh5EyYRTt +hFdioT1JYcIe3XMwOmx3TjADY1jAXAPfeRcjTkMcnZwF76AXUK2XqBANhaG1wjsi +b7ISGU/U5/Jarm2iwQJ5zjKsNm8pZYqpmKsYAVFMErtfcpdLdSp6BG54SrbItcXh +WHfsUs5cuVEi9nCeugLkDzoPLlj/TeouKWOdzhyvLXkPvPmD4/hD0dULTXpCDZhf +73AuQBWTGsWeUnJQiQhDRwuXWhGRX8qFJQ4rzY8rIbaKhge+BQ6BL+pij2uzHKNQ +j12ZLFZgLihLDJogGp08y9Ud6Ru/3WGoFkY38wIDAQABAoIBABM/TczQA8XhteB0 +Tmkfik8qknzDkeInDIKqCZFjKTyS3dBZ2/YzCcHMSxOvFr4ZIXQCF4mnYuExUAdj +G5QaZ43o98AIikae8tSBcitSDI+eFIOIRz1pfTI5B+vQz93AttnHx0GF4/s6GhCx +JbfsuTmDAAahPz9rgZjwUP2F8PLvaAZqJrXBPY+QLWz0SN2zh6vWAHPbJA0sO/4E +oWUhRPXJDf33YCFxnwtbUBie5313suAfNspODcyH+AxBH2FFh63pe0ZGOhX7XFMJ +yxJqujeZrQdfwFZNPXAPVLJGbd7AIOrVE+O8/bYUB/uuj6pPJBqr+Ob/JhY48pRb +VG2qL4ECgYEA9n3PuL13F9XFcLeergGH7fUcSQeD1T6Z1qaI2Wth0Umfmer/fFZh +IKSCSwEGMTLsalFdlTj8jsSAasjuSorQTeSgHjzvzik1Ll2P6syputjsD1RX/nkl +8L50Pwdeey57Y9dgow7Cw/heGYs6dkXLe9H6qM7eoB8Vrk7/TAFuqNECgYEA7uOl +oKyOxeLn005cenc5enY2IxDhXTaAjTGHE64C0lmicD2OZB7/b+ZIb8M5R7GnCNox +4TxLSRhZYOMO/QcTrnSND5PXbX/HLd3nyQRIN1XtBbg7pJooxP/MQ/Ne5XTTMjCg +qPudkOe0ZgUHEcuH8m/YAFY3DDJC50uiXqYtxYMCgYBHfL+ExbZHfGExyp9Duf/x +PHhCmeJbMzessEnaPLF24FJgcm48YlTzAaMkG5zvIeS9BPIOOCPPSCAyWCn8BnxZ +SuhBPM0TzpG067+0ijzjiswTuhN3Iy2kv6e5K+rz8MwqbamCQOKtsVehMub2rFFS +jNiUosKgT8Oa9SBHq9arMQKBgQCE3EVEnFP3iOAILH/QeLiV/GLVk9DTR7mtTUtj +zZayKLnoFMQ5uOe182x8BCa6UfqlOL0fGKqCZ7Fl6kJuxV3T2+yMKlxZAQTk5JLB +wMjtRbPCR5mcTUS5c87GR/eSRCwlsNfZw775VXSGfOtWoUzlsACBB3IsLVP6UZ1n +aKLyQwKBgC61BvKiyGBEYIchqMI4dSF+zCJbSjNUtjwVobcgC6yERZtX2OeLFCoh +NEf9CcL2Eqb+RzwAD3OV65AiZcrThQNXZ8poBxvwWK8I6E6zB+LX7POAvNu/AV/5 +ANnxwHGGLqi+wTcdMZal2iXkdsrno1Ek/nGMCdA7IVs7l5k7fEpG +-----END RSA PRIVATE KEY----- diff --git a/examples/webenginewidgets/clientcertificate/resources/server.pem b/examples/webenginewidgets/clientcertificate/resources/server.pem new file mode 100644 index 000000000..4706fa73e --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/server.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApcCFFNQAgGBu5nr81tUMdXXLGkm8Li/MA0GCSqGSIb3DQEBCwUAMIGU +MQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4x +FzAVBgNVBAoMDlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTES +MBAGA1UEAwwJd3d3LnF0LmlvMSAwHgYJKoZIhvcNAQkBFhFxdHdlYmVuZ2luZUBx +dC5pbzAeFw0yMjExMTYxMjExMTRaFw0zMjExMTMxMjExMTRaMIGSMQswCQYDVQQG +EwJERTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xFzAVBgNVBAoM +DlRoZSBRdCBDb21wYW55MRQwEgYDVQQLDAtRdFdlYkVuZ2luZTEVMBMGA1UEAwwM +c2VydmVyLnF0LmlvMRswGQYJKoZIhvcNAQkBFgxzZXJ2ZXJAcXQuaW8wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDmBCgmvJ6eOa/PLhFuod01ccrwaT/w +u2jVLBPz5ZuHkTJhFO2EV2KhPUlhwh7dczA6bHdOMANjWMBcA995FyNOQxydnAXv +oBdQrZeoEA2FobXCOyJvshIZT9Tn8lqubaLBAnnOMqw2bylliqmYqxgBUUwSu19y +l0t1KnoEbnhKtsi1xeFYd+xSzly5USL2cJ66AuQPOg8uWP9N6i4pY53OHK8teQ+8 ++YPj+EPR1QtNekINmF/vcC5AFZMaxZ5SclCJCENHC5daEZFfyoUlDivNjyshtoqG +B74FDoEv6mKPa7Mco1CPXZksVmAuKEsMmiAanTzL1R3pG7/dYagWRjfzAgMBAAEw +DQYJKoZIhvcNAQELBQADggEBAHotgaBbqIlG4EqjzSpX8kQnZnGJUsA51dbY3K5C +4tNCd+JquQfPmCIKDHkRsmmEU6pcU+LT8m+toJ8Gx0XG4nrdUIDt0Nlf/QrykbPj +hN8z+aSfP9J5tg4NsT7qMWmqUHOa3BcsgWcC4IwWVkbOMz/XbczEQqdBJMbE0+PC +32ihTKPZBPC2QlIvXyuwupvQtcXgEjw1r2FQeYcmItk3CKbJPE/Rk4/aXSCo4b0F +iXPphh8BJPZVvQ2cLpPaGvcse5qjIhF9ODb2HEK3myMwuJVi7teURy8mPlS23Li/ +8gRCNu/stjMlkic7d3dqV0LwaG8+Df1W2wzxsT7IkxN/Z+o= +-----END CERTIFICATE----- diff --git a/examples/webenginewidgets/clientcertificate/resources/server.qrc b/examples/webenginewidgets/clientcertificate/resources/server.qrc new file mode 100644 index 000000000..502afa9cc --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/resources/server.qrc @@ -0,0 +1,7 @@ + + + server.key + server.pem + ca.pem + + diff --git a/examples/webenginewidgets/clientcertificate/server.cpp b/examples/webenginewidgets/clientcertificate/server.cpp new file mode 100644 index 000000000..ee83dab8a --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/server.cpp @@ -0,0 +1,99 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include +#include +#include +#include +#include +#include +#include + +struct Request : public QObject +{ + QByteArray m_data; +}; + +static const QByteArray http_ok(QByteArrayLiteral( + "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n")); +static const QByteArray html_start(QByteArrayLiteral("
")); +static const QByteArray html_end(QByteArrayLiteral("
")); + +int main(int argc, char *argv[]) +{ + QCoreApplication::setOrganizationName("QtExamples"); + QCoreApplication app(argc, argv); + + QSslServer server; + QSslConfiguration configuration(QSslConfiguration::defaultConfiguration()); + configuration.setPeerVerifyMode(QSslSocket::VerifyPeer); + + QFile keyFile(":/resources/server.key"); + keyFile.open(QIODevice::ReadOnly); + + QSslKey key(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + configuration.setPrivateKey(key); + + QList localCerts = QSslCertificate::fromPath(":/resources/server.pem"); + configuration.setLocalCertificateChain(localCerts); + + QList caCerts = QSslCertificate::fromPath(":resources/ca.pem"); + configuration.addCaCertificates(caCerts); + + server.setSslConfiguration(configuration); + + if (!server.listen(QHostAddress::LocalHost, 5555)) + qFatal("Could not start server on localhost:5555"); + else + qInfo("Server started on localhost:5555"); + + QObject::connect(&server, &QTcpServer::pendingConnectionAvailable, [&server]() { + QTcpSocket *socket = server.nextPendingConnection(); + Q_ASSERT(socket); + + QPointer request(new Request); + + QObject::connect(socket, &QAbstractSocket::disconnected, socket, + [socket, request]() mutable { + delete request; + socket->deleteLater(); + }); + + QObject::connect(socket, &QTcpSocket::readyRead, socket, [socket, request]() mutable { + request->m_data.append(socket->readAll()); + + if (!request->m_data.endsWith("\r\n\r\n")) + return; + + socket->write(http_ok); + socket->write(html_start); + + if (request->m_data.startsWith("GET / ")) { + socket->write("

ACCESS GRANTED !

"); + socket->write("

You reached the place, where no one has gone before.

"); + socket->write(""); + } else if (request->m_data.startsWith("GET /exit ")) { + socket->write("

BYE !

"); + socket->write("

Have good day ...

"); + QTimer::singleShot(0, &QCoreApplication::quit); + } else { + socket->write("

There is nothing to see here.

"); + } + + socket->write(html_end); + delete request; + socket->disconnectFromHost(); + }); + }); + + return app.exec(); +} diff --git a/examples/webenginewidgets/clientcertificate/server.pro b/examples/webenginewidgets/clientcertificate/server.pro new file mode 100644 index 000000000..b8fda1717 --- /dev/null +++ b/examples/webenginewidgets/clientcertificate/server.pro @@ -0,0 +1,11 @@ +TEMPLATE = app + +QT += core network +CONFIG += console + +SOURCES += server.cpp + +RESOURCES += resources/server.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/clientcertificate/server +INSTALLS += target diff --git a/examples/webenginewidgets/webenginewidgets.pro b/examples/webenginewidgets/webenginewidgets.pro index ad11d3f5c..c22df271d 100644 --- a/examples/webenginewidgets/webenginewidgets.pro +++ b/examples/webenginewidgets/webenginewidgets.pro @@ -1,4 +1,4 @@ -QT_FOR_CONFIG += webenginecore webenginecore-private +QT_FOR_CONFIG += webenginecore webenginecore-private network-private TEMPLATE=subdirs @@ -26,3 +26,4 @@ qtConfig(webengine-spellchecker):!qtConfig(webengine-native-spellchecker):!cross message("Spellchecker example will not be built because it depends on usage of Hunspell dictionaries.") } +qtConfig(ssl): SUBDIRS += clientcertificate diff --git a/src/core/doc/src/qtwebengine-features.qdoc b/src/core/doc/src/qtwebengine-features.qdoc index 310fe4d92..6588a9d5f 100644 --- a/src/core/doc/src/qtwebengine-features.qdoc +++ b/src/core/doc/src/qtwebengine-features.qdoc @@ -132,6 +132,9 @@ Note that during the \c selectClientCertificate calls, \QWE lists both system and in-memory stored clients certificates. + See also \l{WebEngine Widgets Client Certificate Example}{Client Certificate Example} + for more implementation details. + \section1 Custom Schemes \QWE makes it possible for the application to define its own custom -- cgit v1.2.3