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

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