1// Copyright (C) 2018 Unified Automation GmbH
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qopcuapkiconfiguration.h"
5#include "qopcuaapplicationidentity.h"
6
7#include <QLoggingCategory>
8#include <QSslCertificate>
9#include <QSslCertificateExtension>
10
11QT_BEGIN_NAMESPACE
12
13Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_SECURITY);
14
15/*!
16 \class QOpcUaPkiConfiguration
17 \inmodule QtOpcUa
18 \brief QOpcUaPkiConfiguration defines the PKI configuration of the application.
19 \since QtOpcUa 5.13
20
21 This info must be configured using QOpcUaClient::setPkiConfiguration.
22 The used paths and files must be created beforehand.
23
24 \code
25 QOpcUaPkiConfiguration pkiConfig;
26 const QString pkiDir = QCoreApplication::applicationDirPath() + "/pki";
27
28 pkiConfig.setClientCertificateFile(pkiDir + "/own/certs/application.der");
29 pkiConfig.setPrivateKeyFile(pkiDir + "/own/private/application.pem");
30 pkiConfig.setTrustListDirectory(pkiDir + "/trusted/certs");
31 pkiConfig.setRevocationListDirectory(pkiDir + "/trusted/crl");
32 pkiConfig.setIssuerListDirectory(pkiDir + "/issuers/certs");
33 pkiConfig.setIssuerRevocationListDirectory(pkiDir + "/issuers/crl");
34
35 client->setPkiConfiguration(pkiConfig);
36 \endcode
37*/
38
39class QOpcUaPkiConfigurationData : public QSharedData
40{
41public:
42 QOpcUaPkiConfigurationData() {}
43
44 QString m_clientCertificateFile; /**< Own application certificate filename */
45 QString m_privateKeyFile; /**< Private key filename which belongs to m_certificateFile */
46 QString m_trustListDirectory; /**< Path to trust list directory */
47 QString m_revocationListDirectory; /**< Folder containing certificate revocation list */
48 QString m_issuerListDirectory; /**< Folder containing issuer intermediate certificates (untrusted) */
49 QString m_issuerRevocationListDirectory; /**< Folder containing issuer revocation list */
50};
51
52QOpcUaPkiConfiguration::QOpcUaPkiConfiguration()
53 : data(new QOpcUaPkiConfigurationData())
54{}
55
56QOpcUaPkiConfiguration::~QOpcUaPkiConfiguration()
57{}
58
59/*!
60 Constructs a \l QOpcUaPkiConfiguration from \a other.
61*/
62QOpcUaPkiConfiguration::QOpcUaPkiConfiguration(const QOpcUaPkiConfiguration &other)
63 : data(other.data)
64{}
65
66/*!
67 Sets the values of \a rhs in this PKI configuration.
68*/
69QOpcUaPkiConfiguration &QOpcUaPkiConfiguration::operator=(const QOpcUaPkiConfiguration &rhs)
70{
71 if (this != &rhs)
72 data = rhs.data;
73 return *this;
74}
75
76/*!
77 Returns the file path of the application's client certificate.
78 */
79QString QOpcUaPkiConfiguration::clientCertificateFile() const
80{
81 return data->m_clientCertificateFile;
82}
83
84/*!
85 Sets the file path of the application's client certificate to \a value.
86
87 This file has to be in X509 DER format.
88*/
89void QOpcUaPkiConfiguration::setClientCertificateFile(const QString &value)
90{
91 data->m_clientCertificateFile = value;
92}
93
94/*!
95 Returns the file path of the application's private key.
96*/
97QString QOpcUaPkiConfiguration::privateKeyFile() const
98{
99 return data->m_privateKeyFile;
100}
101
102/*!
103 Sets the file path of the application's private key to \a value.
104
105 This file has to be in X509 PEM format.
106*/
107void QOpcUaPkiConfiguration::setPrivateKeyFile(const QString &value)
108{
109 data->m_privateKeyFile = value;
110}
111
112/*!
113 Returns the folder of the certificate trust list.
114*/
115QString QOpcUaPkiConfiguration::trustListDirectory() const
116{
117 return data->m_trustListDirectory;
118}
119
120/*!
121 Sets the path of the certificate trust list directory to \a value.
122
123 All certificates in this directory will be trusted.
124 Certificates have to be in X509 DER format.
125*/
126void QOpcUaPkiConfiguration::setTrustListDirectory(const QString &value)
127{
128 data->m_trustListDirectory = value;
129}
130
131/*!
132 Returns the path of the certificate revocation list directory.
133*/
134QString QOpcUaPkiConfiguration::revocationListDirectory() const
135{
136 return data->m_revocationListDirectory;
137}
138
139/*!
140 Sets the path of the certificate revocation list directory to \a value.
141*/
142void QOpcUaPkiConfiguration::setRevocationListDirectory(const QString &value)
143{
144 data->m_revocationListDirectory = value;
145}
146
147/*!
148 Returns the path of the intermediate issuer list directory.
149
150 These issuers will not be trusted.
151*/
152QString QOpcUaPkiConfiguration::issuerListDirectory() const
153{
154 return data->m_issuerListDirectory;
155}
156
157/*!
158 Sets the path of the intermediate issuer list directory to \a value.
159*/
160void QOpcUaPkiConfiguration::setIssuerListDirectory(const QString &value)
161{
162 data->m_issuerListDirectory = value;
163}
164
165/*!
166 Returns the path of the intermediate issuer revocation list directory.
167*/
168QString QOpcUaPkiConfiguration::issuerRevocationListDirectory() const
169{
170 return data->m_issuerRevocationListDirectory;
171}
172
173/*!
174 Sets the path of the intermediate issuer revocation list directory to \a value.
175*/
176void QOpcUaPkiConfiguration::setIssuerRevocationListDirectory(const QString &value)
177{
178 data->m_issuerRevocationListDirectory = value;
179}
180
181/*!
182 Returns an application identity based on the application's client certificate.
183
184 The application's identity has to match the used certificate. The returned application
185 identity is prefilled by using information of the configured client certificate.
186*/
187QOpcUaApplicationIdentity QOpcUaPkiConfiguration::applicationIdentity() const
188{
189 QOpcUaApplicationIdentity identity;
190
191 auto certList = QSslCertificate::fromPath(path: clientCertificateFile(), format: QSsl::Der);
192 if (certList.isEmpty()) {
193 qCWarning(QT_OPCUA_SECURITY) << "No client certificate found at" << clientCertificateFile()
194 << ". Application identity will be invalid.";
195 return QOpcUaApplicationIdentity();
196 }
197
198 auto extensions = certList[0].extensions();
199 for (const auto &extension : std::as_const(t&: extensions)) {
200 if (extension.name() == QLatin1String("subjectAltName")) { // OID: 2.5.29.17
201 const auto value = extension.value().toMap();
202 // const QString dns = value[QLatin1String("DNS")].toString();
203 const QString uri = value[QLatin1String("URI")].toString();
204
205 const auto token = uri.split(sep: QChar::fromLatin1(c: ':'), behavior: Qt::SkipEmptyParts);
206
207 if (token.size() != 4) {
208 qCWarning(QT_OPCUA_SECURITY) << "URI string from certificate has unexpected format:"
209 << uri << "Application identity will be invalid.";
210 return QOpcUaApplicationIdentity();
211 }
212
213 identity.setApplicationUri(uri);
214 identity.setApplicationName(token.at(i: 3));
215 identity.setProductUri(QStringLiteral("%1:%2").arg(args: token.at(i: 2), args: token.at(i: 3)));
216 }
217 }
218 return identity;
219}
220
221/*!
222 Return true if the public key information required to validate the server certificate
223 is set.
224*/
225bool QOpcUaPkiConfiguration::isPkiValid() const
226{
227 return !issuerListDirectory().isEmpty() &&
228 !issuerRevocationListDirectory().isEmpty() &&
229 !revocationListDirectory().isEmpty() &&
230 !trustListDirectory().isEmpty();
231}
232
233/*!
234 Returns true if the private key file and client certificate file are set.
235*/
236bool QOpcUaPkiConfiguration::isKeyAndCertificateFileSet() const
237{
238 return !clientCertificateFile().isEmpty() &&
239 !privateKeyFile().isEmpty();
240}
241
242QT_END_NAMESPACE
243

source code of qtopcua/src/opcua/client/qopcuapkiconfiguration.cpp