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