diff options
author | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-11-19 03:00:59 +0100 |
---|---|---|
committer | Qt Forward Merge Bot <qt_forward_merge_bot@qt-project.org> | 2019-11-19 03:00:59 +0100 |
commit | 59554501e666084bc69ee8c99b705d2b7eabf23a (patch) | |
tree | 5e4b5312909cc049c36ece0261311fe11be08abd | |
parent | 93c4fedb026c6f9a753de46f2d982abc95910274 (diff) | |
parent | bcd37d8bffb422e5fcb29f6778c626a0cfc40ec3 (diff) |
Merge remote-tracking branch 'origin/5.14' into 5.15
Change-Id: I387a9d5936a8fa82e545602f08323e87f3b37f00
51 files changed, 8749 insertions, 13 deletions
diff --git a/examples/opcua/opcua.pro b/examples/opcua/opcua.pro index eb396c5..dc5f5f4 100644 --- a/examples/opcua/opcua.pro +++ b/examples/opcua/opcua.pro @@ -2,7 +2,9 @@ TEMPLATE = subdirs qtHaveModule(widgets): SUBDIRS += \ opcuaviewer \ -QT_FOR_CONFIG += opcua-private +QT_FOR_CONFIG += opcua-private core-private + +qtConfig(ssl):!darwin:!winrt: SUBDIRS += x509 qtConfig(open62541) { qtHaveModule(quick): SUBDIRS += waterpump diff --git a/examples/opcua/x509/doc/x509.qdoc b/examples/opcua/x509/doc/x509.qdoc new file mode 100644 index 0000000..506dc82 --- /dev/null +++ b/examples/opcua/x509/doc/x509.qdoc @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the QtOpcUa module. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example x509 + \ingroup qtopcua-examples + \title Qt OPC UA X509 Support Example + \brief Shows how to generate keys and certificate signing requests. + + This example show how client applications can generate their own self-signed certificate + or generate a certificate signing request. +*/ diff --git a/examples/opcua/x509/main.cpp b/examples/opcua/x509/main.cpp new file mode 100644 index 0000000..b246fc3 --- /dev/null +++ b/examples/opcua/x509/main.cpp @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, you may use this file under the terms of the BSD license +** as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QOpcUaProvider> +#include <QOpcUaKeyPair> +#include <QOpcUaX509CertificateSigningRequest> +#include <QOpcUaX509ExtensionSubjectAlternativeName> +#include <QOpcUaX509ExtensionBasicConstraints> +#include <QOpcUaX509ExtensionKeyUsage> +#include <QFile> + +int main(int argc, char **argv) +{ + Q_UNUSED(argc); + Q_UNUSED(argv); + + // Generate RSA Key + QOpcUaKeyPair key; + key.generateRsaKey(QOpcUaKeyPair::RsaKeyStrength::Bits1024); + + // Save private key to file + QByteArray keyData = key.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Aes128Cbc, "password"); + + QFile keyFile("privateKey.pem"); + keyFile.open(QFile::WriteOnly); + keyFile.write(keyData); + keyFile.close(); + + // Create a certificate signing request + QOpcUaX509CertificateSigningRequest csr; + + // Set the subject of the certificate + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CommonName, "QtOpcUaViewer"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, "DE"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, "The Qt Company"); + csr.setSubject(dn); + + // The subject alternative name extension is needed for OPC UA + QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, "foo.com"); + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, "urn:foo.com:The%20Qt%20Company:QtOpcUaViewer"); + san->setCritical(true); + csr.addExtension(san); + + // Set the certificate basic constraints + QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; + bc->setCa(false); + bc->setCritical(true); + csr.addExtension(bc); + + // Set the key usage constraints + QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; + ku->setCritical(true); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); + csr.addExtension(ku); + + // Now there are two options: + // 1. When you need to get your certificate signing request signed by a certificate authority + // you have to use the request data. + // 2. When there is no certificate authority you have to self-sign the request. + + // Option 1 + QByteArray certificateSigingRequestData = csr.createRequest(key); + + // Option 2 + QByteArray selfSignedCertificateData = csr.createSelfSignedCertificate(key); + + return 0; +} diff --git a/examples/opcua/x509/x509.pro b/examples/opcua/x509/x509.pro new file mode 100644 index 0000000..f098656 --- /dev/null +++ b/examples/opcua/x509/x509.pro @@ -0,0 +1,12 @@ +QT += opcua +CONFIG += c++11 +DEPENDPATH += INCLUDEPATH + +SOURCES += main.cpp \ + +#install +target.path = $$[QT_INSTALL_EXAMPLES]/opcua/x509 +INSTALLS += target + +HEADERS += \ + diff --git a/src/opcua/client/client.pri b/src/opcua/client/client.pri index 2363ee1..4b82579 100644 --- a/src/opcua/client/client.pri +++ b/src/opcua/client/client.pri @@ -12,6 +12,7 @@ SOURCES += \ client/qopcuaaddreferenceitem.cpp \ client/qopcuaapplicationdescription.cpp \ client/qopcuaapplicationidentity.cpp \ + client/qopcuaapplicationrecorddatatype.cpp \ client/qopcuaargument.cpp \ client/qopcuaattributeoperand.cpp \ client/qopcuaauthenticationinformation.cpp \ @@ -62,6 +63,7 @@ HEADERS += \ client/qopcuaaddreferenceitem.h \ client/qopcuaapplicationdescription.h \ client/qopcuaapplicationidentity.h \ + client/qopcuaapplicationrecorddatatype.h \ client/qopcuaargument.h \ client/qopcuaattributeoperand.h \ client/qopcuaauthenticationinformation.h \ @@ -106,3 +108,11 @@ HEADERS += \ client/qopcuawriteitem.h \ client/qopcuawriteresult.h \ client/qopcuaxvalue.h \ + +# Only added for platforms that have OpenSSL available +QT_FOR_CONFIG += core-private +qtConfig(ssl):!darwin:!winrt { + PUBLIC_HEADERS += client/qopcuagdsclient.h + SOURCES += client/qopcuagdsclient.cpp + HEADERS += client/qopcuagdsclient_p.h +} diff --git a/src/opcua/client/qopcuaapplicationrecorddatatype.cpp b/src/opcua/client/qopcuaapplicationrecorddatatype.cpp new file mode 100644 index 0000000..6c92fb1 --- /dev/null +++ b/src/opcua/client/qopcuaapplicationrecorddatatype.cpp @@ -0,0 +1,235 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuaapplicationrecorddatatype.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaApplicationRecordDataType + \inmodule QtOpcUa + \brief The OPC UA ApplicationRecordDataType. + \since 5.14 + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + This is the Qt OPC UA representation for the OPC UA ApplicationRecordDataType type defined in OPC-UA version 1.04 part 12, 6.3.5. + It is used to represent a record in the GDS. +*/ + +class QOpcUaApplicationRecordDataTypeData : public QSharedData +{ +public: + QString applicationId; + QString applicationUri; + QOpcUaApplicationDescription::ApplicationType applicationType; + QString productUri; + QVector<QOpcUaLocalizedText> applicationNames; + QVector<QString> discoveryUrls; + QVector<QString> serverCapabilityIdentifiers; +}; + +/*! + Constructs a default ApplicationRecordDataType. +*/ +QOpcUaApplicationRecordDataType::QOpcUaApplicationRecordDataType() + : data(new QOpcUaApplicationRecordDataTypeData) +{ + data->applicationId = QLatin1String("ns=0;i=0"); // empty node id +} + +/*! + Constructs an ApplicationRecordDataType from \a rhs. +*/ +QOpcUaApplicationRecordDataType::QOpcUaApplicationRecordDataType(const QOpcUaApplicationRecordDataType &rhs) + : data(rhs.data) +{ +} + +/*! + Sets the values from \a rhs in this ApplicationRecordDataType. +*/ +QOpcUaApplicationRecordDataType &QOpcUaApplicationRecordDataType::operator=(const QOpcUaApplicationRecordDataType &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Returns \c true if this ApplicationRecordDataType has the same values as \a rhs. +*/ +bool QOpcUaApplicationRecordDataType::operator==(const QOpcUaApplicationRecordDataType &rhs) const +{ + return data->applicationId == rhs.applicationId() + && data->applicationUri == rhs.applicationUri() + && data->applicationType == rhs.applicationType() + && data->productUri == rhs.productUri() + && data->applicationNames == rhs.applicationNames() + && data->discoveryUrls == rhs.discoveryUrls() + && data->serverCapabilityIdentifiers == rhs.serverCapabilityIdentifiers(); +} + +/*! + Converts this ApplicationRecordDataType to \l QVariant. +*/ +QOpcUaApplicationRecordDataType::operator QVariant() const +{ + return QVariant::fromValue(*this); +} + +/*! + Destructs an ApplicationRecordDataType. +*/ +QOpcUaApplicationRecordDataType::~QOpcUaApplicationRecordDataType() +{ +} + +/*! + Sets the application id to \a applicationId. +*/ +void QOpcUaApplicationRecordDataType::setApplicationId(const QString &applicationId) +{ + data->applicationId = applicationId; +} + +/*! + Returns the application id. +*/ +const QString &QOpcUaApplicationRecordDataType::applicationId() const +{ + return data->applicationId; +} + +/*! + Sets the application URI to \a applicationUri. +*/ +void QOpcUaApplicationRecordDataType::setApplicationUri(const QString &applicationUri) +{ + data->applicationUri = applicationUri; +} + +/*! + Returns the application URI. +*/ +const QString &QOpcUaApplicationRecordDataType::applicationUri() const +{ + return data->applicationUri; +} + +/*! + Sets the application type to \a applicationType. +*/ +void QOpcUaApplicationRecordDataType::setApplicationType(QOpcUaApplicationDescription::ApplicationType applicationType) +{ + data->applicationType = applicationType; +} + +/*! + Returns the application type. +*/ +QOpcUaApplicationDescription::ApplicationType QOpcUaApplicationRecordDataType::applicationType() const +{ + return data->applicationType; +} + +/*! + Sets the localized application names to \a applicationNames. +*/ +void QOpcUaApplicationRecordDataType::setApplicationNames(const QVector<QOpcUaLocalizedText> &applicationNames) +{ + data->applicationNames = applicationNames; +} + +/*! + Returns the localized application names. +*/ +const QVector<QOpcUaLocalizedText> &QOpcUaApplicationRecordDataType::applicationNames() const +{ + return data->applicationNames; +} + +/*! + Sets the product URI to \a productUri. +*/ +void QOpcUaApplicationRecordDataType::setProductUri(const QString &productUri) +{ + data->productUri = productUri; +} + +/*! + Returns the product URI. +*/ +const QString &QOpcUaApplicationRecordDataType::productUri() const +{ + return data->productUri; +} + +/*! + Sets the discovery URLs to \a discoverUrls. +*/ +void QOpcUaApplicationRecordDataType::setDiscoveryUrls(const QVector<QString> &discoveryUrls) +{ + data->discoveryUrls = discoveryUrls; +} + +/*! + Returns the discovery URLs. +*/ +const QVector<QString> &QOpcUaApplicationRecordDataType::discoveryUrls() const +{ + return data->discoveryUrls; +} + +/*! + Sets the server capability identifiers to \a serverCapabilityIdentifiers. +*/ +void QOpcUaApplicationRecordDataType::setServerCapabilityIdentifiers(const QVector<QString> &serverCapabilityIdentifiers) +{ + data->serverCapabilityIdentifiers = serverCapabilityIdentifiers; +} + +/*! + Returns the server capability identifiers. +*/ +const QVector<QString> &QOpcUaApplicationRecordDataType::serverCapabilityIdentifiers() const +{ + return data->serverCapabilityIdentifiers; +} + +QT_END_NAMESPACE diff --git a/src/opcua/client/qopcuaapplicationrecorddatatype.h b/src/opcua/client/qopcuaapplicationrecorddatatype.h new file mode 100644 index 0000000..0988fd4 --- /dev/null +++ b/src/opcua/client/qopcuaapplicationrecorddatatype.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAAPPLICATIONRECORDDATATYPE_H +#define QOPCUAAPPLICATIONRECORDDATATYPE_H + +#include <QtOpcUa/qopcuaglobal.h> +#include <QtOpcUa/qopcualocalizedtext.h> +#include <QtOpcUa/qopcuaapplicationdescription.h> + +#include <QtCore/qshareddata.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaApplicationRecordDataTypeData; +class Q_OPCUA_EXPORT QOpcUaApplicationRecordDataType +{ +public: + QOpcUaApplicationRecordDataType(); + QOpcUaApplicationRecordDataType(const QOpcUaApplicationRecordDataType &); + QOpcUaApplicationRecordDataType &operator=(const QOpcUaApplicationRecordDataType &); + bool operator==(const QOpcUaApplicationRecordDataType &rhs) const; + operator QVariant() const; + ~QOpcUaApplicationRecordDataType(); + + void setApplicationId(const QString &nodeId); + const QString &applicationId() const; + + void setApplicationType(QOpcUaApplicationDescription::ApplicationType applicationType); + QOpcUaApplicationDescription::ApplicationType applicationType() const; + + void setApplicationUri(const QString &applicationUri); + const QString &applicationUri() const; + + void setApplicationNames(const QVector<QOpcUaLocalizedText> &applicationNames); + const QVector<QOpcUaLocalizedText> &applicationNames() const; + + void setProductUri(const QString &productUri); + const QString &productUri() const; + + void setDiscoveryUrls(const QVector<QString> &discoveryUrls); + const QVector<QString> &discoveryUrls() const; + + void setServerCapabilityIdentifiers(const QVector<QString> &serverCapabilityIdentifiers); + const QVector<QString> &serverCapabilityIdentifiers() const; + +private: + QSharedDataPointer<QOpcUaApplicationRecordDataTypeData> data; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QOpcUaApplicationRecordDataType) + +#endif // QOPCUAAPPLICATIONRECORDDATATYPE_H diff --git a/src/opcua/client/qopcuabinarydataencoding.cpp b/src/opcua/client/qopcuabinarydataencoding.cpp index 3b2739b..1746ff0 100644 --- a/src/opcua/client/qopcuabinarydataencoding.cpp +++ b/src/opcua/client/qopcuabinarydataencoding.cpp @@ -132,6 +132,10 @@ QT_BEGIN_NAMESPACE \row \li QOpcUaArgument \li Argument + + \row + \li QOpcUaApplicationRecordDataType + \li ApplicationRecordDataType \endtable */ diff --git a/src/opcua/client/qopcuabinarydataencoding.h b/src/opcua/client/qopcuabinarydataencoding.h index e613a0d..8b6e4ff 100644 --- a/src/opcua/client/qopcuabinarydataencoding.h +++ b/src/opcua/client/qopcuabinarydataencoding.h @@ -37,6 +37,7 @@ #ifndef QOPCUABINARYDATAENCODING_H #define QOPCUABINARYDATAENCODING_H +#include <QtOpcUa/qopcuaapplicationrecorddatatype.h> #include <QtOpcUa/qopcuaargument.h> #include <QtOpcUa/qopcuaaxisinformation.h> #include <QtOpcUa/qopcuacomplexnumber.h> @@ -993,6 +994,62 @@ inline bool QOpcUaBinaryDataEncoding::encodeArray(const QVector<T> &src) return true; } +template <> +inline QOpcUaApplicationRecordDataType QOpcUaBinaryDataEncoding::decode<QOpcUaApplicationRecordDataType>(bool &success) +{ + QOpcUaApplicationRecordDataType temp; + + temp.setApplicationId(decode<QString, QOpcUa::Types::NodeId>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setApplicationUri(decode<QString>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setApplicationType(static_cast<QOpcUaApplicationDescription::ApplicationType>(decode<uint32_t>(success))); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setApplicationNames(decodeArray<QOpcUaLocalizedText>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setProductUri(decode<QString>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setDiscoveryUrls(decodeArray<QString>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + temp.setServerCapabilityIdentifiers(decodeArray<QString>(success)); + if (!success) + return QOpcUaApplicationRecordDataType(); + + return temp; +} + +template <> +inline bool QOpcUaBinaryDataEncoding::encode<QOpcUaApplicationRecordDataType>(const QOpcUaApplicationRecordDataType &src) +{ + if (!encode<QString, QOpcUa::NodeId>(src.applicationId())) + return false; + if (!encode<QString>(src.applicationUri())) + return false; + if (!encode<uint32_t>(src.applicationType())) + return false; + if (!encodeArray<QOpcUaLocalizedText>(src.applicationNames())) + return false; + if (!encode<QString>(src.productUri())) + return false; + if (!encodeArray<QString>(src.discoveryUrls())) + return false; + if (!encodeArray<QString>(src.serverCapabilityIdentifiers())) + return false; + return true; +} + QT_END_NAMESPACE #endif // QOPCUABINARYDATAENCODING_H diff --git a/src/opcua/client/qopcuagdsclient.cpp b/src/opcua/client/qopcuagdsclient.cpp new file mode 100644 index 0000000..55cc20c --- /dev/null +++ b/src/opcua/client/qopcuagdsclient.cpp @@ -0,0 +1,1883 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuagdsclient_p.h" +#include <QOpcUaProvider> +#include <QOpcUaExtensionObject> +#include <QOpcUaBinaryDataEncoding> +#include <QOpcUaErrorState> +#include <QOpcUaKeyPair> +#include <QOpcUaX509ExtensionSubjectAlternativeName> +#include <QOpcUaX509ExtensionBasicConstraints> +#include <QOpcUaX509ExtensionKeyUsage> +#include <QFile> +#include <QTimer> +#include <QTemporaryFile> +#include <QSettings> +#include <QSslCertificate> +#include <QtCore/qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(QT_OPCUA_GDSCLIENT, "qt.opcua.gdsclient") + +static const QStringList elementsToResolve { + QLatin1String("GetApplication"), + QLatin1String("FindApplications"), + QLatin1String("RegisterApplication"), + QLatin1String("UnregisterApplication"), + QLatin1String("GetCertificateGroups"), + QLatin1String("GetCertificateStatus"), + QLatin1String("StartSigningRequest"), + QLatin1String("FinishRequest"), + QLatin1String("GetTrustList"), +}; + +/*! + \class QOpcUaGdsClient + \inmodule QtOpcUa + \since 5.14 + + \brief Handles communication with the GDS Server + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + This class handles all steps needed for communication with a GDS server. Provided with + information about the application it does registering with the server and managing + key/certificates. + + Only few details need to be known in order to take part in a secured network. + + First time registration requires administrative privileges using username and password + for authentication. All further authentications are application based, using the certificate + which was received first. + + Expecting the whole process to succeed, you have to wait for the applicationRegistered signal. + + Most of the setup structs have to be the same as for the connection with QOpcUaClient afterwards + and can be shared. + + Setting up a GDS client: + + \code + QOpcUaGdsClient c; + + // In case the credentials are needed + QObject::connect(&c, &QOpcUaGdsClient::authenticationRequired, [&](QOpcUaAuthenticationInformation &authInfo) { + authInfo.setUsernameAuthentication("root", "secret"); + }); + + // Await success + QObject::connect(&c, &QOpcUaGdsClient::applicationRegistered, [&]() { + qDebug() << "Application" << c.applicationId() << "registered"; + }); + + c.setBackend(...); + c.setEndpoint(...); + c.setApplicationIdentity(...); + c.setPkiConfiguration(...); + c.setApplicationRecord(...); + c.setCertificateSigningRequestPresets(...); + c.start(); + \endcode +*/ + +/*! + \enum QOpcUaGdsClient::Error + + This enum is used to specify errors, that could happen during the registration + process. + + \value NoError + Everying is fine + \value InvalidBackend + The backend could not be instantiated. + The backend string given, does not match any backend or loading the plugin failed. + \value InvalidEndpoint + The given endpoint is not valid. + \value ConnectionError + The connection to the server endpoint failed. + \value DirectoryNodeNotFound + The directory node on the server could not be resolved + \value FailedToRegisterApplication + The registration of the application was not successful. + \value FailedToUnregisterApplication + The unregistration of the application was not successful. + \value FailedToGetCertificateStatus + The status of the current certificate could not be retrieved. + \value FailedToGetCertificate + A certificate could not be retrieved from the server. +*/ + +/*! + \enum QOpcUaGdsClient::State + + This enum is used to specify the current state of the registration of the GDS + client. + + \value Idle + The client was not started yet. + \value BackendInstantiated + The backend was instantiated + \value Connecting + A connecting to the server is being made + \value Connected + The connection to the server endpoint was successful. + \value RegisteringApplication + The application is being registered with the server. + \value ApplicationRegistered + Registering the application with the server was successful. + \value Error + An error happened. See the return value of error() and the terminal output + for more details. +*/ + +/*! + \fn QOpcUaGdsClient::stateChanged(State state) + + This signal is emitted when the internal state of the client changes. + The \a state indicates the new state. +*/ + +/*! + \fn QOpcUaGdsClient::errorChanged(Error error) + + This signal is emitted when an error occurred. + The \a state indicates the new state. +*/ + +/*! + \fn QOpcUaGdsClient::applicationRegistered() + + This signal is emitted when an application was registered successfully. +*/ + +/*! + \fn QOpcUaGdsClient::certificateGroupsReceived(QStringList certificateGroups) + + This signal is emitted when the GDS client receives a new list of certificateGroups + for this application. +*/ + +/*! + \fn QOpcUaGdsClient::certificateUpdateRequired() + + This signal is emitted when the GDS client detects that an update of the currently + used certificate is necessary. + + This could be caused by the server, requesting the client to update the certificate, + when the certificate's due date is met or if the certificate is self-signed. + + The certificate update is handled automatically. This signal is only for informational + purpose that an update is going to happen. +*/ + +/*! + \fn QOpcUaGdsClient::certificateUpdated() + + This signal is emitted when the GDS client received a new certificate that was + stored on disk. +*/ + +/*! + \fn QOpcUaGdsClient::unregistered() + + This signal is emitted when the GDS client has unregistered the application. +*/ + +/*! + \fn QOpcUaGdsClient::trustListUpdated() + + This signal is emitted when the GDS client has received a new trust list from the + server and stored to disk. +*/ + +/*! + \fn QOpcUaGdsClient::trustListUpdated() + + This signal is emitted when the GDS client has received a new trust list from the + server and stored to disk. +*/ + +/*! + \fn QOpcUaGdsClient::authenticationRequired(QOpcUaAuthenticationInformation &authInfo) + + This signal is emitted when the GDS client tries to do a first time authentication + with a server, that requires administrative privileges. + + \a authInfo has to be filled with valid authentication information. + This slot must not be used crossing thread boundaries. +*/ + +/* + Step 1 + Create a self-signed certificate for the application. + Previously created key and certificate are reused if present. + + Step 2 + Connect to the given endpoint and wait for the namespace array to be updated. + + Step 3 + Find the root node of the GDS API + + Step 4 + From the root node find the "Directory" node where the method nodes are located. + - resolveDirectoryNode() + - lambda + + Step 5 + Resolve the method nodes that are being used. + - resolveMethodNodes() + - _q_handleResolveBrowsePathFinished() + + Step 6a + In case a previous appliation id is known, check if the application registration is still valid. + In case it is still valid, reuse it. + - getApplication() + - handleGetApplicationFinished() + + Step 6b + In case there is no valid application id known, try to find a previous registration by the application URI. + - findRegisteredApplication() + - handleFindApplicationsFinished() + + Step 6c + In case an application id was not found by the application URI, register the application. + - registerApplication() + - handleRegisterApplicationFinished() + + Step 7 + Get the certificate groups of the application + - getCertificateGroups() + - handleGetCertificateGroupsFinished() + - emit signal certificateGroupsReceived + + Step 8 + Resolve the certificate types from the certificate groups (CertificateGroup->CertificateTypes) + - resolveCertificateTypes() + - lambda + + Step 9 + Read certificate type value from the resolved nodes + - getCertificateTypes() + - lambda + + Step 10 + The application now is using a application id from 6a, 6b or 6c, resolved the certificates and is ready to interact with the GDS. + - registrationDone() + - emit signal applicationRegistered + -> Start a certificate check + -> Start a trust list update + + Depending on the certificate state, the certificate needs to be updated by the client. + This may happen due to + - the server requires it (GetCertificateStatus) + - the expiry date is due + - the certificate is self-signed from the initial connection + + Step 1 + A timer will trigger the certificate check regularly by triggering + - _q_certificateCheckTimeout() + - emit signal certificateUpdateRequired + + Step 2a + Check locally first, if a renew condition is met. + Otherwise ask the server. + + Step 2b + Ask the server if the certificate needs renewal + - getCertificateStatus() + - handleGetCertificateStatusFinished() + - emit signal certificateUpdateRequired + + Step 3 + Start a certificate signing request + - startCertificateRequest() + - handleStartSigningRequestFinished() + + Step 4 + Regularly ask the server for the certificate signing result + - finishCertificateRequest() + - handleFinishRequestFinished() + - emit signal certificateUpdated + This will return a new certificate. +*/ + +/*! + Constructs a GDS client +*/ +QOpcUaGdsClient::QOpcUaGdsClient(QObject *parent) + : QObject(*(new QOpcUaGdsClientPrivate()), parent) +{ + Q_D(QOpcUaGdsClient); + d->initializePrivateConnections(); +} + +/*! + Destructs a GDS client +*/ +QOpcUaGdsClient::~QOpcUaGdsClient() +{ + +} + +/*! + Sets the backend to be used by the client to communicate with the server to \a backend. + + This function has to be called before starting the GDS client. + Changing this setting afterwards has no effect. + + \sa QOpcUaProvider::availableBackends() start() +*/ +void QOpcUaGdsClient::setBackend(const QString &backend) +{ + Q_D(QOpcUaGdsClient); + d->setBackend(backend); +} + +/*! + Returns the current backend setting. + + If the backend was changed after starting the client, it will return + the changed setting, but not the actually used instance. +*/ +const QString &QOpcUaGdsClient::backend() const +{ + Q_D(const QOpcUaGdsClient); + return d->backend(); +} + +/*! + Sets the endpoint to be used by the client to communicate with the server to \a endpoint. + + This function has to be called before starting the GDS client. + Changing this setting afterwards has no effect. + + Communication to a GDS server is only possible through an encrypted endpoint. + Using an unencrypted endpoint will fail. +*/ +void QOpcUaGdsClient::setEndpoint(const QOpcUaEndpointDescription &endpoint) +{ + Q_D(QOpcUaGdsClient); + d->setEndpoint(endpoint); +} + +/*! + Returns the current endpoint setting. + + If the endpoint was changed after starting the client, it will return + the changed setting, but not the actually used endpoint. +*/ +const QOpcUaEndpointDescription &QOpcUaGdsClient::endpoint() const +{ + Q_D(const QOpcUaGdsClient); + return d->endpoint(); +} + +/*! + Sets the PKI configuration to be used by the client. + + All certificates, keys and trust lists will be used from or stored to + the locations given. In order to use the certificate received from + the GDS, the same configuration has to be used with QOpcUaClient. + + This function has to be called before starting the GDS client. + Changing this setting afterwards has no effect. +*/ +void QOpcUaGdsClient::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) +{ + Q_D(QOpcUaGdsClient); + d->setPkiConfiguration(pkiConfig); +} + +/*! + Returns the current pkiConfiguration. +*/ +const QOpcUaPkiConfiguration &QOpcUaGdsClient::pkiConfiguration() const +{ + Q_D(const QOpcUaGdsClient); + return d->pkiConfiguration(); +} + +/*! + Sets the application identity to be used by the client. + + This identity is used to register with the GDS server. + This function has to be called before starting the GDS client. + Changing this setting afterwards has no effect. +*/ +void QOpcUaGdsClient::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) +{ + Q_D(QOpcUaGdsClient); + d->setApplicationIdentity(appIdentity); +} + +/*! + Returns the current applicationIdentity. +*/ +const QOpcUaApplicationIdentity &QOpcUaGdsClient::applicationIdentity() const +{ + Q_D(const QOpcUaGdsClient); + return d->applicationIdentity(); +} + +/*! + Sets the application record data to be used by the client. + + This data is used to register with the GDS server. + This function has to be called before starting the GDS client. + + Most of the data is the same as in the application identity. + After registration the assigned application id can be retrieved. + + \sa setApplicationIdentity +*/ +void QOpcUaGdsClient::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord) +{ + Q_D(QOpcUaGdsClient); + d->setApplicationRecord(appRecord); +} + +/*! + Returns the application record data that is used by the client. +*/ +const QOpcUaApplicationRecordDataType &QOpcUaGdsClient::applicationRecord() const +{ + Q_D(const QOpcUaGdsClient); + return d->applicationRecord(); +} + +/*! + Returns the application id assigned by the server. + + Is is a shortcut to receive the application id from the application record data. + + \sa applicationRecord() +*/ +QString QOpcUaGdsClient::applicationId() const +{ + Q_D(const QOpcUaGdsClient); + return d->applicationId(); +} + +/*! + Sets the presets for certificate siging requests. + + When creating a certificate signing request some additional information is needed, + that is not provided by the application identity. + + This function has to be called before starting the GDS client. + + \sa setApplicationIdentity() +*/ +void QOpcUaGdsClient::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns) +{ + Q_D(QOpcUaGdsClient); + d->setCertificateSigningRequestPresets(dn, dns); +} + +/*! + Returns the distinguished name preset for certificate siging requests. +*/ +const QOpcUaX509DistinguishedName &QOpcUaGdsClient::distinguishedNameCertificateSigningRequestPreset() const +{ + Q_D(const QOpcUaGdsClient); + return d->distinguishedNameCertificateSigningRequestPreset(); +} + +/*! + Returns the DNS preset for certificate siging requests. +*/ +const QString &QOpcUaGdsClient::dnsCertificateSigningRequestPreset() const +{ + Q_D(const QOpcUaGdsClient); + return d->dnsCertificateSigningRequestPreset(); +} + +/*! + Sets the interval in milliseconds for checking the validity of the client certificate + to \a interval. +*/ +void QOpcUaGdsClient::setCertificateCheckInterval(int interval) +{ + Q_D(QOpcUaGdsClient); + d->setCertificateCheckInterval(interval); +} + +/*! + Returns the interval in milliseconds for checking the validity of the client certificate. +*/ +int QOpcUaGdsClient::certificateCheckInterval() const +{ + Q_D(const QOpcUaGdsClient); + return d->certificateCheckInterval(); +} + +/*! + Sets the interval in milliseconds for updating the trust list from the server + to \a interval. +*/ +void QOpcUaGdsClient::setTrustListUpdateInterval(int interval) +{ + Q_D(QOpcUaGdsClient); + d->setTrustListUpdateInterval(interval); +} + +/*! + Returns the interval in milliseconds for updating the trust list from the server. +*/ +int QOpcUaGdsClient::trustListUpdateInterval() const +{ + Q_D(const QOpcUaGdsClient); + return d->trustListUpdateInterval(); +} + +/*! + Returns the current error state. +*/ +QOpcUaGdsClient::Error QOpcUaGdsClient::error() const +{ + Q_D(const QOpcUaGdsClient); + return d->error(); +} + +/*! + Returns the current client state. +*/ +QOpcUaGdsClient::State QOpcUaGdsClient::state() const +{ + Q_D(const QOpcUaGdsClient); + return d->state(); +} + +/*! + Starts the client process. + + After setting up all information, + the client can be started. + + \list + \li setBackend + \li setEndpoing + \li setApplicationIdentity + \li setPkiConfiguration + \li setApplicationRecord + \li setCertificateSigingRequestPresets + \endlist +*/ +void QOpcUaGdsClient::start() +{ + Q_D(QOpcUaGdsClient); + return d->start(); +} + +/*! + Unregisters an application from the server. + + This function can be used when an application has to be removed permanently from + the network. It does not need to be called when rebooting or shutting down. +*/ +void QOpcUaGdsClient::unregisterApplication() +{ + Q_D(QOpcUaGdsClient); + return d->unregisterApplication(); +} + +QOpcUaGdsClientPrivate::QOpcUaGdsClientPrivate() + : QObjectPrivate() + , m_certificateCheckTimer(new QTimer) + , m_trustListUpdateTimer(new QTimer) +{ + m_certificateCheckTimer->setInterval(60*60*1000); + m_trustListUpdateTimer->setInterval(60*60*1000); +} + +QOpcUaGdsClientPrivate::~QOpcUaGdsClientPrivate() +{ + delete m_client; + delete m_directoryNode; + delete m_certificateGroupNode; + delete m_certificateTypesNode; + delete m_certificateFinishTimer; + delete m_certificateCheckTimer; + delete m_trustListUpdateTimer; + delete m_trustListNode; +} + +void QOpcUaGdsClientPrivate::initializePrivateConnections() +{ + Q_Q(QOpcUaGdsClient); + QObject::connect(m_certificateCheckTimer, SIGNAL(timeout()), q, SLOT(_q_certificateCheckTimeout())); + QObject::connect(m_trustListUpdateTimer, SIGNAL(timeout()), q, SLOT(_q_updateTrustList())); +} + +void QOpcUaGdsClientPrivate::setEndpoint(const QOpcUaEndpointDescription &endpoint) +{ + m_endpoint = endpoint; +} + +const QOpcUaEndpointDescription &QOpcUaGdsClientPrivate::endpoint() const +{ + return m_endpoint; +} + +void QOpcUaGdsClientPrivate::setBackend(const QString &backend) +{ + m_backend = backend; +} + +const QString &QOpcUaGdsClientPrivate::backend() const +{ + return m_backend; +} + +QString QOpcUaGdsClientPrivate::applicationId() const +{ + return m_appRecord.applicationId(); +} + +void QOpcUaGdsClientPrivate::setError(QOpcUaGdsClient::Error error) +{ + Q_Q(QOpcUaGdsClient); + m_error = error; + setState(QOpcUaGdsClient::State::Error); + emit q->errorChanged(m_error); +} + +void QOpcUaGdsClientPrivate::setState(QOpcUaGdsClient::State state) +{ + Q_Q(QOpcUaGdsClient); + m_state = state; + emit q->stateChanged(state); +} + +void QOpcUaGdsClientPrivate::start() +{ + Q_Q(QOpcUaGdsClient); + + if (m_backend.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Backend name not set"; + setError(QOpcUaGdsClient::Error::InvalidBackend); + return; + } + + if (!m_client) { + QOpcUaProvider provider; + setState(QOpcUaGdsClient::State::BackendInstantiated); + m_client = provider.createClient(m_backend); + if (!m_client) { + setError(QOpcUaGdsClient::Error::InvalidBackend); + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid backend"; + return; + } + + QObject::connect(m_client, &QOpcUaClient::namespaceArrayUpdated, [this]() { + setState(QOpcUaGdsClient::State::Connected); + this->resolveDirectoryNode(); + }); + + QObject::connect(m_client, &QOpcUaClient::errorChanged, q, [this](QOpcUaClient::ClientError error) { + if (error == QOpcUaClient::InvalidUrl) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid URL"; + setError(QOpcUaGdsClient::Error::InvalidEndpoint); + } else { + qCWarning(QT_OPCUA_GDSCLIENT) << "Connection error"; + setError(QOpcUaGdsClient::Error::ConnectionError); + } + }); + + QObject::connect(m_client, &QOpcUaClient::disconnected, q, [this]() { + delete m_directoryNode; + m_directoryNode = nullptr; + m_directoryNodes.clear(); + setState(QOpcUaGdsClient::State::Idle); + if (m_restartRequired) { + m_restartRequired = false; + this->start(); + } + }); + + QObject::connect(m_client, &QOpcUaClient::connectError, [](QOpcUaErrorState *errorState) { + // Ignore all client side errors and continue + if (errorState->isClientSideError()) + errorState->setIgnoreError(); + }); + } + + setState(QOpcUaGdsClient::State::Connecting); + if (m_endpoint.endpointUrl().isEmpty()) { + setError(QOpcUaGdsClient::Error::InvalidEndpoint); + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid endpoint"; + return; + } + + QOpcUaKeyPair keyPair; + QFile keyFile(m_pkiConfig.privateKeyFile()); + + if (!keyFile.exists()) { + // File does not exist: Create a key + qCDebug(QT_OPCUA_GDSCLIENT) << "Creating a key"; + keyPair.generateRsaKey(QOpcUaKeyPair::RsaKeyStrength::Bits1024); + + if (!keyFile.open(QFile::WriteOnly | QFile::NewOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for writing:" << keyFile.errorString(); + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + auto data = keyPair.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Unencrypted, QString()); + + if (!keyFile.resize(data.size())) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + if (!keyFile.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set permissions"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + keyFile.write(data); + keyFile.close(); + } else { + qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key" << keyFile.fileName(); + + if (!keyFile.open(QFile::ReadOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for reading:" << keyFile.errorString(); + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + auto data = keyFile.readAll(); + keyFile.close(); + + if (!keyPair.loadFromPemData(data)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + if (!keyPair.hasPrivateKey()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + } + + QFile certFile(m_pkiConfig.clientCertificateFile()); + if (!certFile.exists()) { + qCDebug(QT_OPCUA_GDSCLIENT) << "Creating self-signed certificate in" << certFile.fileName(); + + auto csr = createSigningRequest(); + csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); + auto selfSigned = csr.createSelfSignedCertificate(keyPair); + + if (!certFile.open(QFile::WriteOnly | QFile::NewOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open certificate file" << certFile.fileName() << "for writing:" << certFile.errorString(); + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + if (!certFile.resize(selfSigned.size())) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to set file size 2"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + certFile.write(selfSigned); + certFile.close(); + } + + // Load persistent data + QSettings settings; + qCDebug(QT_OPCUA_GDSCLIENT) << "Using settings from" << settings.fileName(); + const auto applicationId = settings.value(QLatin1String("gds/applicationId"), QString()).toString(); + if (applicationId.isEmpty()) + qCInfo(QT_OPCUA_GDSCLIENT) << "No application ID in persistent storage"; + else + m_appRecord.setApplicationId(applicationId); + + m_client->setApplicationIdentity(m_appIdentitiy); + m_client->setPkiConfiguration(m_pkiConfig); + m_client->connectToEndpoint(m_endpoint); +} + +void QOpcUaGdsClientPrivate::resolveDirectoryNode() +{ + Q_Q(QOpcUaGdsClient); + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + delete m_directoryNode; + m_directoryNode = m_client->node(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::ObjectsFolder)); // ns=0;i=85 + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Root node not found"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + m_gdsNamespaceIndex = m_client->namespaceArray().indexOf(QLatin1String("http://opcfoundation.org/UA/GDS/")); + if (m_gdsNamespaceIndex < 0) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Namespace not found"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + QObject::connect(m_directoryNode, &QOpcUaNode::resolveBrowsePathFinished, [this, q](QVector<QOpcUaBrowsePathTarget> targets, + QVector<QOpcUaRelativePathElement> path, + QOpcUa::UaStatusCode statusCode) { + m_directoryNode->deleteLater(); + m_directoryNode = nullptr; + + if (path.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (path[0].targetName().name() != QLatin1String("Directory")) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name(); + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (targets.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (!targets[0].isFullyResolved()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + m_directoryNode = m_client->node(targets[0].targetId()); + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid directory node"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + QObject::connect(m_directoryNode, SIGNAL(resolveBrowsePathFinished(QVector<QOpcUaBrowsePathTarget>, QVector<QOpcUaRelativePathElement>, QOpcUa::UaStatusCode)), + q, SLOT(_q_handleResolveBrowsePathFinished(QVector<QOpcUaBrowsePathTarget>, QVector<QOpcUaRelativePathElement>, QOpcUa::UaStatusCode))); + QObject::connect(m_directoryNode, SIGNAL(methodCallFinished(QString, QVariant, QOpcUa::UaStatusCode)), + q, SLOT(_q_handleDirectoryNodeMethodCallFinished(QString, QVariant, QOpcUa::UaStatusCode))); + + qCDebug(QT_OPCUA_GDSCLIENT) << "Directory node resolved:" << m_directoryNode->nodeId(); + this->resolveMethodNodes(); + }); + + QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String("Directory")), + QOpcUa::ReferenceTypeId::Organizes); + QVector<QOpcUaRelativePathElement> browsePath { pathElement }; + + if (!m_directoryNode->resolveBrowsePath(browsePath)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to resolve directory node"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + delete m_directoryNode; + m_directoryNode = nullptr; + return; + } +} + +void QOpcUaGdsClientPrivate::resolveMethodNodes() +{ + // See OPC UA Specification 1.04 part 12 6.3.2 "Directory" + + QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(m_gdsNamespaceIndex, QLatin1String()), + QOpcUa::ReferenceTypeId::HasComponent); + QVector<QOpcUaRelativePathElement> browsePath { pathElement }; + + + // Resolve all needed nodes from the directory + for (const auto &key : qAsConst(elementsToResolve)) { + if (!m_directoryNodes.value(key).isEmpty()) + continue; // Already resolved + + auto target = browsePath[0].targetName(); + target.setName(key); + browsePath[0].setTargetName(target); + + if (!m_directoryNode->resolveBrowsePath(browsePath)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve Directory node"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + } +} + +void QOpcUaGdsClientPrivate::_q_handleResolveBrowsePathFinished(QVector<QOpcUaBrowsePathTarget> targets, QVector<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode) { + if (path.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (m_directoryNodes.contains(path[0].targetName().name()) || !elementsToResolve.contains(path[0].targetName().name())) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name(); + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving directory failed" << statusCode; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (targets.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + if (!targets[0].isFullyResolved()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Directory not fully resolved"; + setError(QOpcUaGdsClient::Error::DirectoryNodeNotFound); + return; + } + + m_directoryNodes[path[0].targetName().name()] = targets[0].targetId().nodeId(); + + if (elementsToResolve.size() == m_directoryNodes.size()) { + qCDebug(QT_OPCUA_GDSCLIENT) << "All symbols resolved"; + + if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) + this->findRegisteredApplication(); + else + this->getApplication(); + } +} + +void QOpcUaGdsClientPrivate::getApplication() +{ + // See OPC UA Specification 1.04 part 12 6.3.9 "GetApplication" + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("GetApplication")], + QVector<QPair<QVariant, QOpcUa::Types>> { qMakePair(QVariant(m_appRecord.applicationId()), QOpcUa::Types::NodeId) })) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method GetApplication"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } +} + +void QOpcUaGdsClientPrivate::handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + if (statusCode == QOpcUa::BadNotFound) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No application with ID" << m_appRecord.applicationId() << "registered"; + // Clear application ID and register + m_appRecord.setApplicationId(QString()); + + // Remove invalid id from settings + QSettings settings; + settings.remove(QLatin1String("gds/applicationId")); + settings.sync(); + + restartWithCredentials(); + return; + } + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Get application failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + auto extensionObject = result.value<QOpcUaExtensionObject>(); + if (!extensionObject.encodingTypeId().isEmpty()) { + if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId(); + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + auto buffer = extensionObject.encodedBody(); + QOpcUaBinaryDataEncoding decoder(&buffer); + bool ok; + QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(ok); + + if (!ok) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + if (m_appRecord.applicationId() != appRecord.applicationId()) + qCWarning(QT_OPCUA_GDSCLIENT) << "Returned application record contains a different application ID" << m_appRecord.applicationId() << appRecord.applicationId(); + + // FIXME: To be removed + appRecord.setApplicationId(m_appRecord.applicationId()); + + m_appRecord = appRecord; + qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << m_appRecord.applicationId() << "which is already registered at the server"; + } + getCertificateGroups(); +} + +void QOpcUaGdsClientPrivate::findRegisteredApplication() +{ + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("FindApplications")], + QVector<QPair<QVariant, QOpcUa::Types>> { qMakePair(QVariant(m_appRecord.applicationUri()), QOpcUa::Types::String) })) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } +} + +void QOpcUaGdsClientPrivate::handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Find application failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + auto extensionObject = result.value<QOpcUaExtensionObject>(); + + if (extensionObject.encodingTypeId().isEmpty()) { + qCInfo(QT_OPCUA_GDSCLIENT) << "No application with URI" << m_appIdentitiy.applicationUri() << "registered"; + this->registerApplication(); + return; + } + + if (extensionObject.encodingTypeId() != QOpcUa::nodeIdFromInteger(m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Unexpected return type:" << extensionObject.encodingTypeId(); + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + auto buffer = extensionObject.encodedBody(); + QOpcUaBinaryDataEncoding decoder(&buffer); + bool ok; + QOpcUaApplicationRecordDataType appRecord = decoder.decode<QOpcUaApplicationRecordDataType>(ok); + + if (!ok) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to decode data"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + m_appRecord = appRecord; + qCInfo(QT_OPCUA_GDSCLIENT) << "Reusing application ID" << appRecord.applicationId() << "registered for URI" << appRecord.applicationUri(); + getCertificateGroups(); +} + +void QOpcUaGdsClientPrivate::registerApplication() +{ + if (!m_appRecord.applicationId().isEmpty() && m_appRecord.applicationId() != QLatin1String("ns=0;i=0")) + return; + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + QByteArray buffer; + QOpcUaBinaryDataEncoding encoder(&buffer); + if (!encoder.encode(m_appRecord)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to encode body"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + QOpcUaExtensionObject parameter; + parameter.setBinaryEncodedBody(buffer, QOpcUa::nodeIdFromInteger(m_gdsNamespaceIndex, ApplicationRecordDataType_Encoding_DefaultBinary)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("RegisterApplication")], + QVector<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(parameter, QOpcUa::ExtensionObject) })) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method RegisterApplication"; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + } +} + +void QOpcUaGdsClientPrivate::handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + if (statusCode != QOpcUa::Good) { + qCDebug(QT_OPCUA_GDSCLIENT) << "Requesting user credentials"; + if (statusCode == QOpcUa::BadUserAccessDenied && m_client->authenticationInformation().authenticationType() == QOpcUaUserTokenPolicy::Anonymous) { + restartWithCredentials(); + return; + } + qCWarning(QT_OPCUA_GDSCLIENT) << "Register application failed with" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToRegisterApplication); + return; + } + + m_appRecord.setApplicationId(result.toString()); + + QSettings settings; + settings.setValue(QLatin1String("gds/applicationId"), m_appRecord.applicationId()); + settings.sync(); + + qCInfo(QT_OPCUA_GDSCLIENT) << "Registered application with id" << m_appRecord.applicationId(); + getCertificateGroups(); +} + +void QOpcUaGdsClientPrivate::getCertificateGroups() +{ + // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.6 GetCertificateGroups + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QVector<QOpcUa::TypedVariant> arguments; + arguments.push_back(QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("GetCertificateGroups")], arguments)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateGroups"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + } +} + +void QOpcUaGdsClientPrivate::handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + Q_Q(QOpcUaGdsClient); + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate groups failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + m_certificateGroups = result.value<QStringList>(); + qCDebug(QT_OPCUA_GDSCLIENT) << "Certificate groups:" << m_certificateGroups; + + emit q->certificateGroupsReceived(m_certificateGroups); + resolveCertificateTypes(); +} + +void QOpcUaGdsClientPrivate::resolveCertificateTypes() +{ + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (m_certificateGroups.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate groups is empty"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + delete m_certificateGroupNode; + m_certificateGroupNode = m_client->node(m_certificateGroups[0]); + + if (!m_certificateGroupNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate node failed"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QObject::connect(m_certificateGroupNode, &QOpcUaNode::resolveBrowsePathFinished, [this](QVector<QOpcUaBrowsePathTarget> targets, + QVector<QOpcUaRelativePathElement> path, + QOpcUa::UaStatusCode statusCode) { + if (path.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid path size"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (path[0].targetName().name() != QLatin1String("CertificateTypes")) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid resolve name" << path[0].targetName().name(); + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Resolving certificate types failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (targets.size() != 1) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Invalid number of results"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (!targets[0].isFullyResolved()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types not fully resolved"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + m_certificateTypesNodeId = targets[0].targetId().nodeId(); + this->getCertificateTypes(); + }); + + QOpcUaRelativePathElement pathElement(QOpcUaQualifiedName(0, QLatin1String("CertificateTypes")), + QOpcUa::ReferenceTypeId::Unspecified); + QVector<QOpcUaRelativePathElement> browsePath { pathElement }; + + + if (!m_certificateGroupNode->resolveBrowsePath(browsePath)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not resolve certificate type"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } +} + +void QOpcUaGdsClientPrivate::getCertificateTypes() +{ + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (m_certificateTypesNodeId.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node id is empty"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + delete m_certificateTypesNode; + m_certificateTypesNode = m_client->node(m_certificateTypesNodeId); + + if (!m_certificateTypesNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificate types node failed"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QObject::connect(m_certificateTypesNode, &QOpcUaNode::attributeUpdated, [this](QOpcUa::NodeAttribute attr, QVariant value) { + if (attr == QOpcUa::NodeAttribute::Value) + qCWarning(QT_OPCUA_GDSCLIENT) << "possible certificate types" << value; + registrationDone(); + }); + + if (!m_certificateTypesNode->readValueAttribute()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read certificate types"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } +} + +void QOpcUaGdsClientPrivate::registrationDone() +{ + Q_Q(QOpcUaGdsClient); + + setState(QOpcUaGdsClient::State::ApplicationRegistered); + emit q->applicationRegistered(); + + m_certificateCheckTimer->start(); + _q_certificateCheckTimeout(); // Force a check now + + m_trustListUpdateTimer->start(); + _q_updateTrustList(); // Force a check now +} + +void QOpcUaGdsClientPrivate::getCertificateStatus() +{ + // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.8 GetCertificateStatus + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (m_certificateGroups.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate groups received"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QString certificateType = QLatin1String("ns=0;i=0"); // null node + + QVector<QOpcUa::TypedVariant> arguments; + arguments.push_back(QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); + // Let the server choose the certificate group id + arguments.push_back(QOpcUa::TypedVariant(m_certificateGroups[0], QOpcUa::NodeId)); + // Let the server choose the certificate type id + arguments.push_back(QOpcUa::TypedVariant(certificateType, QOpcUa::NodeId)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("GetCertificateStatus")], arguments)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetCertificateStatus"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + } +} + +void QOpcUaGdsClientPrivate::handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + Q_Q(QOpcUaGdsClient); + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate status failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + if (result.toBool()) { + qInfo(QT_OPCUA_GDSCLIENT) << "Certificate needs update"; + emit q->certificateUpdateRequired(); + startCertificateRequest(); + } +} + +void QOpcUaGdsClientPrivate::startCertificateRequest() +{ + // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.3 StartSigningRequest + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QOpcUaKeyPair keyPair; + QFile keyFile(m_pkiConfig.privateKeyFile()); + + qCDebug(QT_OPCUA_GDSCLIENT) << "Using private key" << keyFile.fileName(); + + if (!keyFile.open(QFile::ReadOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to open private key file" << keyFile.fileName() << "for reading:" << keyFile.errorString(); + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + auto data = keyFile.readAll(); + keyFile.close(); + + if (!keyPair.loadFromPemData(data)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load private key"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + if (!keyPair.hasPrivateKey()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Private key does not contain a private key"; + setError(QOpcUaGdsClient::Error::ConnectionError); + return; + } + + auto csr = createSigningRequest(); + csr.setEncoding(QOpcUaX509CertificateSigningRequest::Encoding::DER); + const auto csrData = csr.createRequest(keyPair); + + + QVector<QOpcUa::TypedVariant> arguments; + arguments.push_back(QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); + // Let the server choose the certificate group id + arguments.push_back(QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId)); + // Let the server choose the certificate type id + arguments.push_back(QOpcUa::TypedVariant(QLatin1String("ns=0;i=0"), QOpcUa::NodeId)); + + arguments.push_back(QOpcUa::TypedVariant(csrData, QOpcUa::ByteString)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("StartSigningRequest")], arguments)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call StartSigningRequest"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + } +} + +void QOpcUaGdsClientPrivate::handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + m_certificateRequestId = result.toString(); + + if (!m_certificateFinishTimer) + m_certificateFinishTimer = new QTimer; + + m_certificateFinishTimer->setInterval(2000); + m_certificateFinishTimer->setSingleShot(true); + QObject::connect(m_certificateFinishTimer, &QTimer::timeout, [this]() { + this->finishCertificateRequest(); + }); + + m_certificateFinishTimer->start(); +} + +void QOpcUaGdsClientPrivate::finishCertificateRequest() +{ + // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.5 FinishRequest + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (m_certificateRequestId.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate request id"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QVector<QOpcUa::TypedVariant> arguments; + arguments.push_back(QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); + arguments.push_back(QOpcUa::TypedVariant(m_certificateRequestId, QOpcUa::NodeId)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("FinishRequest")], arguments)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call FinishRequest"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + } +} + +void QOpcUaGdsClientPrivate::handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + Q_Q(QOpcUaGdsClient); + + if (statusCode == QOpcUa::BadNothingToDo) { + // Server not finished yet: Try again later + m_certificateFinishTimer->setInterval(m_certificateFinishTimer->interval() * 2); + m_certificateFinishTimer->start(); + qCWarning(QT_OPCUA_GDSCLIENT) << "Server not finished yet: Trying again in" << m_certificateFinishTimer->interval() / 1000 << "s"; + return; + } + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Getting certificate failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + m_certificateRequestId.clear(); + const auto resultList = result.toList(); + + if (resultList.size() != 3) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Expected list of 3 results but got" << resultList.size(); + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + const auto certificate = resultList[0].toByteArray(); + //const auto privateKey = resultList[1].toByteArray(); + const auto issuerList = resultList[2].toByteArray(); + + if (certificate.isEmpty() || issuerList.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Certificates are empty"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + qCInfo(QT_OPCUA_GDSCLIENT) << "Received new certificate" << certificate; + + QFile certificateFile(m_pkiConfig.clientCertificateFile()); + if (!certificateFile.open(QFile::WriteOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + if (certificateFile.write(certificate) != certificate.size()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + certificateFile.close(); + + // FIMXE: How to store this? + QTemporaryFile issuerFile(m_pkiConfig.issuerListDirectory() + QLatin1String("/XXXXXX.der")); + if (!issuerFile.open()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store issuer certificates to" << m_pkiConfig.issuerListDirectory(); + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + if (issuerFile.write(issuerList) != issuerList.size()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not store certificate data"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + qCDebug(QT_OPCUA_GDSCLIENT) << "issuer list stored to" << issuerFile.fileName(); + issuerFile.close(); + issuerFile.setAutoRemove(false); + + emit q->certificateUpdated(); +} + +void QOpcUaGdsClientPrivate::unregisterApplication() +{ + Q_Q(QOpcUaGdsClient); + + if (m_appRecord.applicationId().isEmpty() || m_appRecord.applicationId() == QLatin1String("ns=0;i=0")) { + emit q->unregistered(); + return; + } + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); + return; + } + + if (!m_directoryNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No directory node"; + setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); + return; + } + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("UnregisterApplication")], + QVector<QOpcUa::TypedVariant> { QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId) })) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call method UnregisterApplication"; + setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); + } +} + +void QOpcUaGdsClientPrivate::handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + Q_Q(QOpcUaGdsClient); + Q_UNUSED(result); + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Unregister application failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToUnregisterApplication); + return; + } + + m_client->disconnectFromEndpoint(); + emit q->unregistered(); +} + +void QOpcUaGdsClientPrivate::setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig) +{ + m_pkiConfig = pkiConfig; +} + +const QOpcUaPkiConfiguration &QOpcUaGdsClientPrivate::pkiConfiguration() const +{ + return m_pkiConfig; +} + +void QOpcUaGdsClientPrivate::setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity) +{ + m_appIdentitiy = appIdentity; +} + +const QOpcUaApplicationIdentity &QOpcUaGdsClientPrivate::applicationIdentity() const +{ + return m_appIdentitiy; +} + +void QOpcUaGdsClientPrivate::setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord) +{ + m_appRecord = appRecord; +} + +const QOpcUaApplicationRecordDataType &QOpcUaGdsClientPrivate::applicationRecord() const +{ + return m_appRecord; +} + +void QOpcUaGdsClientPrivate::setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns) +{ + csrPresets.dn = dn; + csrPresets.dns = dns; +} + +const QOpcUaX509DistinguishedName &QOpcUaGdsClientPrivate::distinguishedNameCertificateSigningRequestPreset() const +{ + return csrPresets.dn; +} + +const QString &QOpcUaGdsClientPrivate::dnsCertificateSigningRequestPreset() const +{ + return csrPresets.dns; +} + +void QOpcUaGdsClientPrivate::setCertificateCheckInterval(int interval) +{ + m_certificateCheckTimer->setInterval(interval); +} + +int QOpcUaGdsClientPrivate::certificateCheckInterval() const +{ + return m_certificateCheckTimer->interval(); +} + +void QOpcUaGdsClientPrivate::setTrustListUpdateInterval(int interval) +{ + m_trustListUpdateTimer->setInterval(interval); +} + +int QOpcUaGdsClientPrivate::trustListUpdateInterval() const +{ + return m_trustListUpdateTimer->interval(); +} + +QOpcUaGdsClient::Error QOpcUaGdsClientPrivate::error() const +{ + return m_error; +} + +QOpcUaGdsClient::State QOpcUaGdsClientPrivate::state() const +{ + return m_state; +} + +QOpcUaX509CertificateSigningRequest QOpcUaGdsClientPrivate::createSigningRequest() const +{ + QOpcUaX509CertificateSigningRequest csr; + QOpcUaX509DistinguishedName dn = csrPresets.dn; + // Overwrite the application name because it has to match the identity + dn.setEntry(QOpcUaX509DistinguishedName::Type::CommonName, m_appIdentitiy.applicationName()); + csr.setSubject(dn); + + QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, m_appIdentitiy.applicationUri()); + if (!csrPresets.dns.isEmpty()) + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, csrPresets.dns); + san->setCritical(true); + csr.addExtension(san); + + QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; + bc->setCa(false); + bc->setCritical(true); + csr.addExtension(bc); + + QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; + ku->setCritical(true); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); + csr.addExtension(ku); + + return csr; +} + +void QOpcUaGdsClientPrivate::_q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode) +{ + if (methodNodeId == m_directoryNodes[QLatin1String("UnregisterApplication")]) { + this->handleUnregisterApplicationFinished(result, statusCode); + } else if ( methodNodeId == m_directoryNodes[QLatin1String("FinishRequest")]) { + this->handleFinishRequestFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("StartSigningRequest")]) { + this->handleStartSigningRequestFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateStatus")]) { + this->handleGetCertificateStatusFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("GetCertificateGroups")]) { + this->handleGetCertificateGroupsFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("RegisterApplication")]) { + this->handleRegisterApplicationFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("FindApplications")]) { + this->handleFindApplicationsFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("GetApplication")]) { + this->handleGetApplicationFinished(result, statusCode); + } else if (methodNodeId == m_directoryNodes[QLatin1String("GetTrustList")]) { + this->handleGetTrustListFinished(result, statusCode); + } else { + qCWarning(QT_OPCUA_GDSCLIENT) << "Result unexpeced method call received" << methodNodeId; + } +} + +void QOpcUaGdsClientPrivate::_q_certificateCheckTimeout() +{ + Q_Q(QOpcUaGdsClient); + + QFile file(m_pkiConfig.clientCertificateFile()); + if (!file.open(QFile::ReadOnly)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile(); + } + + QSslCertificate cert(&file, QSsl::Der); + if (cert.isNull()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to load certificate from file" << m_pkiConfig.clientCertificateFile(); + return; + } + + bool needsUpdate = false; + + if (cert.isSelfSigned()) { + needsUpdate = true; + qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate is self-signed: requesting update"; + } + + if (QDateTime::currentDateTime().addSecs(24*60*60) > cert.expiryDate()) { + needsUpdate = true; + qCInfo(QT_OPCUA_GDSCLIENT) << "Certificate expiry date is due: requesting update"; + } + + if (!needsUpdate) + getCertificateStatus(); + + if (needsUpdate) { + emit q->certificateUpdateRequired(); + startCertificateRequest(); + } +} + +void QOpcUaGdsClientPrivate::_q_updateTrustList() +{ + // OPC UA Specification Version 1.04 Part 4 Chapter 7.6.7 GetTrustList + + if (!m_client || m_client->state() != QOpcUaClient::Connected) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No connection"; + return; + } + + if (!m_certificateGroupNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "No certificate group node"; + return; + } + + QVector<QOpcUa::TypedVariant> arguments; + arguments.push_back(QOpcUa::TypedVariant(m_appRecord.applicationId(), QOpcUa::NodeId)); + arguments.push_back(QOpcUa::TypedVariant(m_certificateGroupNode->nodeId(), QOpcUa::NodeId)); + + if (!m_directoryNode->callMethod(m_directoryNodes[QLatin1String("GetTrustList")], arguments)) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Failed to call GetTrustList"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + } +} + +void QOpcUaGdsClientPrivate::handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode) +{ + Q_Q(QOpcUaGdsClient); + + if (statusCode != QOpcUa::Good) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Getting trust list node failed" << statusCode; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + const auto trustListNodeId = result.toString(); + + if (trustListNodeId.isEmpty()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node id is empty"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificate); + return; + } + + delete m_trustListNode; + m_trustListNode = m_client->node(trustListNodeId); + + if (!m_trustListNode) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Trust list node failed"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } + + QObject::connect(m_trustListNode, &QOpcUaNode::attributeUpdated, [this, q](QOpcUa::NodeAttribute attr, QVariant value) { + Q_UNUSED(value); + if (attr == QOpcUa::NodeAttribute::Value) + emit q->trustListUpdated(); + }); + + if (!m_trustListNode->readValueAttribute()) { + qCWarning(QT_OPCUA_GDSCLIENT) << "Could not read trust list"; + setError(QOpcUaGdsClient::Error::FailedToGetCertificateStatus); + return; + } +} + +// When it is detected that authentication credentials are required this function +// is used to re-connect to the server after requesting credentials +void QOpcUaGdsClientPrivate::restartWithCredentials() +{ + Q_Q(QOpcUaGdsClient); + + QOpcUaAuthenticationInformation authInfo; + emit q->authenticationRequired(authInfo); + m_client->setAuthenticationInformation(authInfo); + + qCInfo(QT_OPCUA_GDSCLIENT) << "Restarting connection with credentials"; + m_restartRequired = true; + m_client->disconnectFromEndpoint(); +} + +QT_END_NAMESPACE + +#include "moc_qopcuagdsclient.cpp" diff --git a/src/opcua/client/qopcuagdsclient.h b/src/opcua/client/qopcuagdsclient.h new file mode 100644 index 0000000..e4281d2 --- /dev/null +++ b/src/opcua/client/qopcuagdsclient.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAGDSCLIENT_H +#define QOPCUAGDSCLIENT_H + +#include <QtCore/QObject> +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaApplicationIdentity; +class QOpcUaAuthenticationInformation; +class QOpcUaEndpointDescription; +class QOpcUaPkiConfiguration; +class QOpcUaGdsClientPrivate; +class QOpcUaApplicationRecordDataType; +class QOpcUaX509DistinguishedName; + +class Q_OPCUA_EXPORT QOpcUaGdsClient : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QOpcUaGdsClient) + +public: + enum class Error { + NoError, + InvalidBackend, + InvalidEndpoint, + ConnectionError, + DirectoryNodeNotFound, + FailedToRegisterApplication, + FailedToUnregisterApplication, + FailedToGetCertificateStatus, + FailedToGetCertificate, + }; + Q_ENUM(Error) + + enum class State { + Idle, + BackendInstantiated, + Connecting, + Connected, + RegisteringApplication, + ApplicationRegistered, + // + Error, + }; + Q_ENUM(State) + + QOpcUaGdsClient(QObject *parent = nullptr); + virtual ~QOpcUaGdsClient(); + + void setBackend(const QString &backend); + const QString &backend() const; + + void setEndpoint(const QOpcUaEndpointDescription &endpoint); + const QOpcUaEndpointDescription &endpoint() const; + + void setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig); + const QOpcUaPkiConfiguration &pkiConfiguration() const; + + void setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity); + const QOpcUaApplicationIdentity &applicationIdentity() const; + + void setApplicationRecord(const QOpcUaApplicationRecordDataType &appRecord); + const QOpcUaApplicationRecordDataType &applicationRecord() const; + + QString applicationId() const; + + void setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns); + const QOpcUaX509DistinguishedName &distinguishedNameCertificateSigningRequestPreset() const; + const QString &dnsCertificateSigningRequestPreset() const; + + void setCertificateCheckInterval(int interval); + int certificateCheckInterval() const; + + void setTrustListUpdateInterval(int interval); + int trustListUpdateInterval() const; + + Error error() const; + State state() const; + + void start(); + void unregisterApplication(); + +Q_SIGNALS: + void stateChanged(State); + void errorChanged(Error); + void applicationRegistered(); + void certificateGroupsReceived(QStringList certificateGroups); + void certificateUpdateRequired(); + void certificateUpdated(); + void unregistered(); + void trustListUpdated(); + void authenticationRequired(QOpcUaAuthenticationInformation &authInfo); + +private: + Q_PRIVATE_SLOT(d_func(), void _q_handleDirectoryNodeMethodCallFinished(QString, QVariant, QOpcUa::UaStatusCode)) + Q_PRIVATE_SLOT(d_func(), void _q_handleResolveBrowsePathFinished(QVector<QOpcUaBrowsePathTarget>, QVector<QOpcUaRelativePathElement>, QOpcUa::UaStatusCode)) + Q_PRIVATE_SLOT(d_func(), void _q_certificateCheckTimeout()) + Q_PRIVATE_SLOT(d_func(), void _q_updateTrustList()) +}; + +QT_END_NAMESPACE + +#endif // QOPCUAGDSCLIENT_H + diff --git a/src/opcua/client/qopcuagdsclient_p.h b/src/opcua/client/qopcuagdsclient_p.h new file mode 100644 index 0000000..1a8155e --- /dev/null +++ b/src/opcua/client/qopcuagdsclient_p.h @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAGDSCLIENTPRIVATE_H +#define QOPCUAGDSCLIENTPRIVATE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QOpcUaClient> +#include <QOpcUaAuthenticationInformation> +#include <QOpcUaApplicationRecordDataType> +#include <QOpcUaEndpointDescription> +#include <QOpcUaPkiConfiguration> +#include <private/qobject_p.h> +#include "qopcuagdsclient.h" +#include <QOpcUaX509CertificateSigningRequest> + +QT_BEGIN_NAMESPACE + +class QTimer; + +#define ApplicationRecordDataType_Encoding_DefaultBinary 134 + +class QOpcUaGdsClientPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QOpcUaGdsClient) +public: + QOpcUaGdsClientPrivate(); + ~QOpcUaGdsClientPrivate(); + + void initializePrivateConnections(); + + void setEndpoint(const QOpcUaEndpointDescription &url); + const QOpcUaEndpointDescription &endpoint() const; + + void setBackend(const QString &); + const QString &backend() const; + + void setPkiConfiguration(const QOpcUaPkiConfiguration &pkiConfig); + const QOpcUaPkiConfiguration &pkiConfiguration() const; + + void setApplicationIdentity(const QOpcUaApplicationIdentity &appIdentity); + const QOpcUaApplicationIdentity &applicationIdentity() const; + + void setApplicationRecord(const QOpcUaApplicationRecordDataType &appIdentity); + const QOpcUaApplicationRecordDataType &applicationRecord() const; + + void setCertificateSigningRequestPresets(const QOpcUaX509DistinguishedName &dn, const QString &dns); + const QOpcUaX509DistinguishedName &distinguishedNameCertificateSigningRequestPreset() const; + const QString &dnsCertificateSigningRequestPreset() const; + + void setCertificateCheckInterval(int timeout); + int certificateCheckInterval() const; + + void setTrustListUpdateInterval(int interval); + int trustListUpdateInterval() const; + + void start(); + void unregisterApplication(); + QString applicationId() const; + + QOpcUaGdsClient::Error error() const; + QOpcUaGdsClient::State state() const; + + +private: + void setError(QOpcUaGdsClient::Error); + void setState(QOpcUaGdsClient::State); + void _q_handleDirectoryNodeMethodCallFinished(QString methodNodeId, QVariant result, QOpcUa::UaStatusCode statusCode); + void _q_handleResolveBrowsePathFinished(QVector<QOpcUaBrowsePathTarget> targets, QVector<QOpcUaRelativePathElement> path, QOpcUa::UaStatusCode statusCode); + void _q_certificateCheckTimeout(); + void _q_updateTrustList(); + + void getApplication(); + void registerApplication(); + void resolveMethodNodes(); + void resolveDirectoryNode(); + void findRegisteredApplication(); + void getCertificateGroups(); + void resolveCertificateTypes(); + void getCertificateTypes(); + void getCertificateStatus(); + void startCertificateRequest(); + void finishCertificateRequest(); + void localCertificateCheck(); + void registrationDone(); + void restartWithCredentials(); + void handleUnregisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleFinishRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleStartSigningRequestFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleGetCertificateStatusFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleGetCertificateGroupsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleRegisterApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleFindApplicationsFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleGetApplicationFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + void handleGetTrustListFinished(const QVariant &result, QOpcUa::UaStatusCode statusCode); + QOpcUaX509CertificateSigningRequest createSigningRequest() const; + + QOpcUaClient *m_client = nullptr; + QOpcUaEndpointDescription m_endpoint; + QOpcUaPkiConfiguration m_pkiConfig; + QOpcUaApplicationIdentity m_appIdentitiy; + QOpcUaApplicationRecordDataType m_appRecord; + QString m_backend; + QStringList m_certificateGroups; + QOpcUaGdsClient::Error m_error = QOpcUaGdsClient::Error::NoError; + QOpcUaGdsClient::State m_state = QOpcUaGdsClient::State::Idle; + QMap<QString, QString> m_directoryNodes; + QOpcUaNode *m_directoryNode = nullptr; + QOpcUaNode *m_certificateGroupNode = nullptr; + QString m_certificateTypesNodeId; + int m_gdsNamespaceIndex = -1; + QOpcUaNode *m_certificateTypesNode = nullptr; + QOpcUaNode *m_trustListNode = nullptr; + QTimer *m_certificateFinishTimer = nullptr; + QTimer *m_certificateCheckTimer = nullptr; + QString m_certificateRequestId; + QTimer *m_trustListUpdateTimer = nullptr; + bool m_restartRequired = false; + QString m_configFilePath; + + struct { + QOpcUaX509DistinguishedName dn; + QString dns; + } csrPresets; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAGDSCLIENTPRIVATE_H + diff --git a/src/opcua/client/qopcuapkiconfiguration.cpp b/src/opcua/client/qopcuapkiconfiguration.cpp index 746aa75..f5cc371 100644 --- a/src/opcua/client/qopcuapkiconfiguration.cpp +++ b/src/opcua/client/qopcuapkiconfiguration.cpp @@ -58,12 +58,12 @@ Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_SECURITY); QOpcUaPkiConfiguration pkiConfig; const QString pkiDir = QCoreApplication::applicationDirPath() + "/pki"; - pkiConfig.setClientCertificateDirectory(pkidir + "/own/certs/application.der"); - pkiConfig.setPrivateKeyDirectory(pkidir + "/own/private/application.pem"); - pkiConfig.setTrustListDirectory(pkidir + "/trusted/certs"); - pkiConfig.setRevocationListLocation(pkidir + "/trusted/crl"); - pkiConfig.setIssuerListDirectory(pkidir + "/issuers/certs"); - pkiConfig.setIssuerRevocationListDirectory(pkidir + "/issuers/crl"); + pkiConfig.setClientCertificateFile(pkiDir + "/own/certs/application.der"); + pkiConfig.setPrivateKeyFile(pkiDir + "/own/private/application.pem"); + pkiConfig.setTrustListDirectory(pkiDir + "/trusted/certs"); + pkiConfig.setRevocationListDirectory(pkiDir + "/trusted/crl"); + pkiConfig.setIssuerListDirectory(pkiDir + "/issuers/certs"); + pkiConfig.setIssuerRevocationListDirectory(pkiDir + "/issuers/crl"); client->setPkiConfiguration(pkiConfig); \endcode diff --git a/src/opcua/opcua.pro b/src/opcua/opcua.pro index 52daeb8..a67ff03 100644 --- a/src/opcua/opcua.pro +++ b/src/opcua/opcua.pro @@ -1,9 +1,11 @@ TARGET = QtOpcUa -QT += core-private network +QT += core-private network network-private QT -= gui +QT_FOR_CONFIG += core-private include(core/core.pri) include(client/client.pri) +qtConfig(ssl):!darwin:!winrt: include(x509/x509.pri) MODULE_PLUGIN_TYPES = opcua QMAKE_DOCS = $$PWD/doc/qtopcua.qdocconf diff --git a/src/opcua/x509/openssl_symbols.cpp b/src/opcua/x509/openssl_symbols.cpp new file mode 100644 index 0000000..b3207a4 --- /dev/null +++ b/src/opcua/x509/openssl_symbols.cpp @@ -0,0 +1,1335 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2016 Richard J. Moore <rich@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#include "openssl_symbols_p.h" +#include <QtCore/qurl.h> + +#ifdef Q_OS_WIN +# include <private/qsystemlibrary_p.h> +#elif QT_CONFIG(library) +# include <QtCore/qlibrary.h> +#endif +#include <QtCore/qmutex.h> +#include <QtCore/qdatetime.h> +#if defined(Q_OS_UNIX) +#include <QtCore/qdir.h> +#endif +#include <QtCore/private/qmemory_p.h> +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +#include <link.h> +#endif +#ifdef Q_OS_DARWIN +#include "private/qcore_mac_p.h" +#endif + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcSsl, "qt.opcua.ssl"); + + +/* + Note to maintainer: + ------------------- + + We load OpenSSL symbols dynamically. Because symbols are known to + disappear, and signatures sometimes change, between releases, we need to + be careful about how this is done. To ensure we don't end up dereferencing + null function pointers, and continue running even if certain functions are + missing, we define helper functions for each of the symbols we load from + OpenSSL, all prefixed with "q_" (declared in + qsslsocket_openssl_symbols_p.h). So instead of calling SSL_connect + directly, we call q_SSL_connect, which is a function that checks if the + actual SSL_connect fptr is null, and returns a failure if it is, or calls + SSL_connect if it isn't. + + This requires a somewhat tedious process of declaring each function we + want to call in OpenSSL thrice: once with the q_, in _p.h, once using the + DEFINEFUNC macros below, and once in the function that actually resolves + the symbols, below the DEFINEFUNC declarations below. + + There's one DEFINEFUNC macro declared for every number of arguments + exposed by OpenSSL (feel free to extend when needed). The easiest thing to + do is to find an existing entry that matches the arg count of the function + you want to import, and do the same. + + The first macro arg is the function return type. The second is the + verbatim name of the function/symbol. Then follows a list of N pairs of + argument types with a variable name, and just the variable name (char *a, + a, char *b, b, etc). Finally there's two arguments - a suitable return + statement for the error case (for an int function, return 0 or return -1 + is usually right). Then either just "return" or DUMMYARG, the latter being + for void functions. + + Note: Take into account that these macros and declarations are processed + at compile-time, and the result depends on the OpenSSL headers the + compiling host has installed, but the symbols are resolved at run-time, + possibly with a different version of OpenSSL. +*/ + +#ifndef QT_LINKED_OPENSSL + +namespace { +void qsslSocketUnresolvedSymbolWarning(const char *functionName) +{ + qCWarning(lcSsl, "QSslSocket: cannot call unresolved function %s", functionName); +} + +#if QT_CONFIG(library) +void qsslSocketCannotResolveSymbolWarning(const char *functionName) +{ + qCWarning(lcSsl, "QSslSocket: cannot resolve %s", functionName); +} +#endif + +} + +#endif // QT_LINKED_OPENSSL + +#if QT_CONFIG(opensslv11) + +// Below are the functions first introduced in version 1.1: + +DEFINEFUNC(const unsigned char *, ASN1_STRING_get0_data, const ASN1_STRING *a, a, return nullptr, return) +DEFINEFUNC2(int, OPENSSL_init_ssl, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC2(int, OPENSSL_init_crypto, uint64_t opts, opts, const OPENSSL_INIT_SETTINGS *settings, settings, return 0, return) +DEFINEFUNC(BIO *, BIO_new, const BIO_METHOD *a, a, return nullptr, return) +DEFINEFUNC(const BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, BN_is_word, BIGNUM *a, a, BN_ULONG w, w, return 0, return) +DEFINEFUNC(int, EVP_CIPHER_CTX_reset, EVP_CIPHER_CTX *c, c, return 0, return) +DEFINEFUNC(int, EVP_PKEY_base_id, EVP_PKEY *a, a, return NID_undef, return) +DEFINEFUNC(int, RSA_bits, RSA *a, a, return 0, return) +DEFINEFUNC(int, DSA_bits, DSA *a, a, return 0, return) +DEFINEFUNC(int, OPENSSL_sk_num, OPENSSL_STACK *a, a, return -1, return) +DEFINEFUNC2(void, OPENSSL_sk_pop_free, OPENSSL_STACK *a, a, void (*b)(void*), b, return, DUMMYARG) +DEFINEFUNC(OPENSSL_STACK *, OPENSSL_sk_new_null, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void, OPENSSL_sk_push, OPENSSL_STACK *a, a, void *b, b, return, DUMMYARG) +DEFINEFUNC(void, OPENSSL_sk_free, OPENSSL_STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(void *, OPENSSL_sk_value, OPENSSL_STACK *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, SSL_session_reused, SSL *a, a, return 0, return) +DEFINEFUNC2(unsigned long, SSL_CTX_set_options, SSL_CTX *ctx, ctx, unsigned long op, op, return 0, return) +#ifdef TLS1_3_VERSION +DEFINEFUNC2(int, SSL_CTX_set_ciphersuites, SSL_CTX *ctx, ctx, const char *str, str, return 0, return) +#endif +DEFINEFUNC3(size_t, SSL_get_client_random, SSL *a, a, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC3(size_t, SSL_SESSION_get_master_key, const SSL_SESSION *ses, ses, unsigned char *out, out, size_t outlen, outlen, return 0, return) +DEFINEFUNC6(int, CRYPTO_get_ex_new_index, int class_index, class_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) +DEFINEFUNC2(unsigned long, SSL_set_options, SSL *ssl, ssl, unsigned long op, op, return 0, return) + +DEFINEFUNC(const SSL_METHOD *, TLS_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_client_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const SSL_METHOD *, TLS_server_method, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, X509_up_ref, X509 *a, a, return, DUMMYARG) +DEFINEFUNC(ASN1_TIME *, X509_getm_notBefore, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_TIME *, X509_getm_notAfter, X509 *a, a, return nullptr, return) +DEFINEFUNC(long, X509_get_version, X509 *a, a, return -1, return) +DEFINEFUNC(EVP_PKEY *, X509_get_pubkey, X509 *a, a, return nullptr, return) +DEFINEFUNC2(void, X509_STORE_set_verify_cb, X509_STORE *a, a, X509_STORE_CTX_verify_cb verify_cb, verify_cb, return, DUMMYARG) +DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get0_chain, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC3(void, CRYPTO_free, void *str, str, const char *file, file, int line, line, return, DUMMYARG) +DEFINEFUNC(long, OpenSSL_version_num, void, DUMMYARG, return 0, return) +DEFINEFUNC(const char *, OpenSSL_version, int a, a, return nullptr, return) +DEFINEFUNC(unsigned long, SSL_SESSION_get_ticket_lifetime_hint, const SSL_SESSION *session, session, return 0, return) +DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG) +DEFINEFUNC(int, DH_bits, DH *dh, dh, return 0, return) + +DEFINEFUNC2(void, BIO_set_data, BIO *a, a, void *ptr, ptr, return, DUMMYARG) +DEFINEFUNC(void *, BIO_get_data, BIO *a, a, return nullptr, return) +DEFINEFUNC2(void, BIO_set_init, BIO *a, a, int init, init, return, DUMMYARG) +DEFINEFUNC(int, BIO_get_shutdown, BIO *a, a, return -1, return) +DEFINEFUNC2(void, BIO_set_shutdown, BIO *a, a, int shut, shut, return, DUMMYARG) + + +#else // QT_CONFIG(opensslv11) + +// Functions below are either deprecated or removed in OpenSSL >= 1.1: + +DEFINEFUNC(unsigned char *, ASN1_STRING_data, ASN1_STRING *a, a, return nullptr, return) + +#ifdef SSLEAY_MACROS +DEFINEFUNC3(void *, ASN1_dup, i2d_of_void *a, a, d2i_of_void *b, b, char *c, c, return nullptr, return) +#endif +DEFINEFUNC2(BIO *, BIO_new_file, const char *filename, filename, const char *mode, mode, return nullptr, return) +DEFINEFUNC(void, ERR_clear_error, DUMMYARG, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(BIO *, BIO_new, BIO_METHOD *a, a, return nullptr, return) +DEFINEFUNC(BIO_METHOD *, BIO_s_mem, void, DUMMYARG, return nullptr, return) +DEFINEFUNC(int, CRYPTO_num_locks, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(void, CRYPTO_set_locking_callback, void (*a)(int, int, const char *, int), a, return, DUMMYARG) +DEFINEFUNC(void, CRYPTO_set_id_callback, unsigned long (*a)(), a, return, DUMMYARG) +DEFINEFUNC(void, CRYPTO_free, void *a, a, return, DUMMYARG) +DEFINEFUNC(unsigned long, ERR_peek_last_error, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(void, ERR_free_strings, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(void, EVP_CIPHER_CTX_cleanup, EVP_CIPHER_CTX *a, a, return, DUMMYARG) +DEFINEFUNC(void, EVP_CIPHER_CTX_init, EVP_CIPHER_CTX *a, a, return, DUMMYARG) + +#ifdef SSLEAY_MACROS +DEFINEFUNC6(void *, PEM_ASN1_read_bio, d2i_of_void *a, a, const char *b, b, BIO *c, c, void **d, d, pem_password_cb *e, e, void *f, f, return nullptr, return) +DEFINEFUNC6(void *, PEM_ASN1_write_bio, d2i_of_void *a, a, const char *b, b, BIO *c, c, void **d, d, pem_password_cb *e, e, void *f, f, return nullptr, return) +#endif // SSLEAY_MACROS + +DEFINEFUNC(int, sk_num, STACK *a, a, return -1, return) +DEFINEFUNC2(void, sk_pop_free, STACK *a, a, void (*b)(void*), b, return, DUMMYARG) + +DEFINEFUNC(_STACK *, sk_new_null, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void, sk_push, _STACK *a, a, void *b, b, return, DUMMYARG) +DEFINEFUNC(void, sk_free, _STACK *a, a, return, DUMMYARG) +DEFINEFUNC2(void *, sk_value, STACK *a, a, int b, b, return nullptr, return) + +DEFINEFUNC(int, SSL_library_init, void, DUMMYARG, return -1, return) +DEFINEFUNC(void, SSL_load_error_strings, void, DUMMYARG, return, DUMMYARG) + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L +DEFINEFUNC5(int, SSL_get_ex_new_index, long argl, argl, void *argp, argp, CRYPTO_EX_new *new_func, new_func, CRYPTO_EX_dup *dup_func, dup_func, CRYPTO_EX_free *free_func, free_func, return -1, return) +#endif // OPENSSL_VERSION_NUMBER >= 0x10001000L + +DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get_chain, X509_STORE_CTX *a, a, return nullptr, return) + +#ifdef SSLEAY_MACROS +DEFINEFUNC2(int, i2d_DSAPrivateKey, const DSA *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC2(int, i2d_RSAPrivateKey, const RSA *a, a, unsigned char **b, b, return -1, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC2(int, i2d_ECPrivateKey, const EC_KEY *a, a, unsigned char **b, b, return -1, return) +#endif +DEFINEFUNC3(RSA *, d2i_RSAPrivateKey, RSA **a, a, unsigned char **b, b, long c, c, return nullptr, return) +DEFINEFUNC3(DSA *, d2i_DSAPrivateKey, DSA **a, a, unsigned char **b, b, long c, c, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC3(EC_KEY *, d2i_ECPrivateKey, EC_KEY **a, a, unsigned char **b, b, long c, c, return nullptr, return) +#endif +#endif + +DEFINEFUNC(char *, CONF_get1_default_config_file, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, OPENSSL_add_all_algorithms_noconf, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(void, OPENSSL_add_all_algorithms_conf, void, DUMMYARG, return, DUMMYARG) +DEFINEFUNC(long, SSLeay, void, DUMMYARG, return 0, return) +DEFINEFUNC(const char *, SSLeay_version, int a, a, return nullptr, return) +DEFINEFUNC(void, ERR_load_crypto_strings, void, DUMMYARG, return, return) + +#endif // QT_CONFIG(opensslv11) + +DEFINEFUNC(long, ASN1_INTEGER_get, ASN1_INTEGER *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_INTEGER_cmp, const ASN1_INTEGER *a, a, const ASN1_INTEGER *b, b, return 1, return) +DEFINEFUNC(int, ASN1_STRING_length, ASN1_STRING *a, a, return 0, return) +DEFINEFUNC2(int, ASN1_STRING_to_UTF8, unsigned char **a, a, ASN1_STRING *b, b, return 0, return) +DEFINEFUNC4(long, BIO_ctrl, BIO *a, a, int b, b, long c, c, void *d, d, return -1, return) +DEFINEFUNC(int, BIO_free, BIO *a, a, return 0, return) +DEFINEFUNC(void, BIO_free_all, BIO *a, a, return, return) +DEFINEFUNC2(BIO *, BIO_new_mem_buf, void *a, a, int b, b, return nullptr, return) +DEFINEFUNC3(int, BIO_read, BIO *a, a, void *b, b, int c, c, return -1, return) + +DEFINEFUNC3(int, BIO_write, BIO *a, a, const void *b, b, int c, c, return -1, return) +DEFINEFUNC(int, BN_num_bits, const BIGNUM *a, a, return 0, return) +DEFINEFUNC2(BN_ULONG, BN_mod_word, const BIGNUM *a, a, BN_ULONG w, w, return static_cast<BN_ULONG>(-1), return) +DEFINEFUNC2(int, BN_set_word, const BIGNUM *a, a, BN_ULONG w, w, return 0, return) +DEFINEFUNC(BIGNUM *, BN_new, void, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, BN_clear, BIGNUM *bignum, bignum, return, return) +DEFINEFUNC(void, BN_free, BIGNUM *bignum, bignum, return, return) +DEFINEFUNC(void, BN_clear_free, BIGNUM *bignum, bignum, return, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC(const EC_GROUP*, EC_KEY_get0_group, const EC_KEY* k, k, return nullptr, return) +DEFINEFUNC(int, EC_GROUP_get_degree, const EC_GROUP* g, g, return 0, return) +#endif +DEFINEFUNC(DSA *, DSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DSA_free, DSA *a, a, return, DUMMYARG) +DEFINEFUNC3(X509 *, d2i_X509, X509 **a, a, const unsigned char **b, b, long c, c, return nullptr, return) +DEFINEFUNC2(char *, ERR_error_string, unsigned long a, a, char *b, b, return nullptr, return) +DEFINEFUNC3(void, ERR_error_string_n, unsigned long e, e, char *b, b, size_t len, len, return, DUMMYARG) +DEFINEFUNC(unsigned long, ERR_get_error, DUMMYARG, DUMMYARG, return 0, return) +DEFINEFUNC(EVP_CIPHER_CTX *, EVP_CIPHER_CTX_new, void, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, EVP_CIPHER_CTX_free, EVP_CIPHER_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, EVP_CIPHER_CTX_ctrl, EVP_CIPHER_CTX *ctx, ctx, int type, type, int arg, arg, void *ptr, ptr, return 0, return) +DEFINEFUNC2(int, EVP_CIPHER_CTX_set_key_length, EVP_CIPHER_CTX *ctx, ctx, int keylen, keylen, return 0, return) +DEFINEFUNC5(int, EVP_CipherInit, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *type, type, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC6(int, EVP_CipherInit_ex, EVP_CIPHER_CTX *ctx, ctx, const EVP_CIPHER *cipher, cipher, ENGINE *impl, impl, const unsigned char *key, key, const unsigned char *iv, iv, int enc, enc, return 0, return) +DEFINEFUNC5(int, EVP_CipherUpdate, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, const unsigned char *in, in, int inl, inl, return 0, return) +DEFINEFUNC3(int, EVP_CipherFinal, EVP_CIPHER_CTX *ctx, ctx, unsigned char *out, out, int *outl, outl, return 0, return) +DEFINEFUNC(const EVP_MD *, EVP_get_digestbyname, const char *name, name, return nullptr, return) +DEFINEFUNC(const EVP_MD *, EVP_sha1, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_MD *, EVP_sha256, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_256_gcm, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC3(int, EVP_PKEY_assign, EVP_PKEY *a, a, int b, b, char *c, c, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_RSA, EVP_PKEY *a, a, RSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DSA, EVP_PKEY *a, a, DSA *b, b, return -1, return) +DEFINEFUNC2(int, EVP_PKEY_set1_DH, EVP_PKEY *a, a, DH *b, b, return -1, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC2(int, EVP_PKEY_set1_EC_KEY, EVP_PKEY *a, a, EC_KEY *b, b, return -1, return) +#endif +DEFINEFUNC(void, EVP_PKEY_free, EVP_PKEY *a, a, return, DUMMYARG) +DEFINEFUNC(DSA *, EVP_PKEY_get1_DSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(RSA *, EVP_PKEY_get1_RSA, EVP_PKEY *a, a, return nullptr, return) +DEFINEFUNC(DH *, EVP_PKEY_get1_DH, EVP_PKEY *a, a, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC(EC_KEY *, EVP_PKEY_get1_EC_KEY, EVP_PKEY *a, a, return nullptr, return) +#endif +DEFINEFUNC(EVP_PKEY *, EVP_PKEY_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(int, EVP_PKEY_type, int a, a, return NID_undef, return) +DEFINEFUNC2(int, i2d_X509, X509 *a, a, unsigned char **b, b, return -1, return) +DEFINEFUNC(const char *, OBJ_nid2sn, int a, a, return nullptr, return) +DEFINEFUNC(const char *, OBJ_nid2ln, int a, a, return nullptr, return) +DEFINEFUNC(int, OBJ_sn2nid, const char *s, s, return 0, return) +DEFINEFUNC(int, OBJ_ln2nid, const char *s, s, return 0, return) +DEFINEFUNC3(int, i2t_ASN1_OBJECT, char *a, a, int b, b, ASN1_OBJECT *c, c, return -1, return) +DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d, return -1, return) + +DEFINEFUNC(int, OBJ_obj2nid, const ASN1_OBJECT *a, a, return NID_undef, return) +DEFINEFUNC4(X509_EXTENSION* , X509V3_EXT_conf_nid, LHASH_OF(CONF_VALUE) *conf, conf, X509V3_CTX *ctx, ctx, int ext_nid, ext_nid, char *value, value, return NULL, return) + +#ifndef SSLEAY_MACROS +DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PrivateKey, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(DSA *, PEM_read_bio_DSAPrivateKey, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSAPrivateKey, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC4(EC_KEY *, PEM_read_bio_ECPrivateKey, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#endif +DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC7(int, PEM_write_bio_DSAPrivateKey, BIO *a, a, DSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_RSAPrivateKey, BIO *a, a, RSA *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_PrivateKey, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +DEFINEFUNC7(int, PEM_write_bio_PKCS8PrivateKey, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC7(int, PEM_write_bio_ECPrivateKey, BIO *a, a, EC_KEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return) +#endif +#endif // !SSLEAY_MACROS +DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PUBKEY, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(DSA *, PEM_read_bio_DSA_PUBKEY, BIO *a, a, DSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +DEFINEFUNC4(RSA *, PEM_read_bio_RSA_PUBKEY, BIO *a, a, RSA **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC4(EC_KEY *, PEM_read_bio_EC_PUBKEY, BIO *a, a, EC_KEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return) +#endif +DEFINEFUNC2(int, PEM_write_bio_DSA_PUBKEY, BIO *a, a, DSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_RSA_PUBKEY, BIO *a, a, RSA *b, b, return 0, return) +DEFINEFUNC2(int, PEM_write_bio_PUBKEY, BIO *a, a, EVP_PKEY *b, b, return 0, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC2(int, PEM_write_bio_EC_PUBKEY, BIO *a, a, EC_KEY *b, b, return 0, return) +#endif +DEFINEFUNC2(void, RAND_seed, const void *a, a, int b, b, return, DUMMYARG) +DEFINEFUNC(int, RAND_status, void, DUMMYARG, return -1, return) +DEFINEFUNC2(int, RAND_bytes, unsigned char *b, b, int n, n, return 0, return) +DEFINEFUNC(RSA *, RSA_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC4(int, RSA_generate_key_ex, RSA *rsa, rsa, int bits, bits, BIGNUM *e, e, BN_GENCB *cb, cb, return 0, return) +DEFINEFUNC(void, RSA_free, RSA *a, a, return, DUMMYARG) +DEFINEFUNC(X509_STORE *, SSL_CTX_get_cert_store, const SSL_CTX *a, a, return nullptr, return) +DEFINEFUNC2(int, X509_cmp, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC4(int, X509_digest, const X509 *x509, x509, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return -1, return) +#ifndef SSLEAY_MACROS +DEFINEFUNC(X509 *, X509_dup, X509 *a, a, return nullptr, return) +#endif +DEFINEFUNC2(void, X509_print, BIO *a, a, X509 *b, b, return, DUMMYARG); +DEFINEFUNC(ASN1_OBJECT *, X509_EXTENSION_get_object, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, X509_free, X509 *a, a, return, DUMMYARG) +//Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +DEFINEFUNC2(ASN1_TIME *, X509_gmtime_adj, ASN1_TIME *s, s, long adj, adj, return nullptr, return) +DEFINEFUNC(void, ASN1_TIME_free, ASN1_TIME *t, t, return, DUMMYARG) +DEFINEFUNC2(X509_EXTENSION *, X509_get_ext, X509 *a, a, int b, b, return nullptr, return) +DEFINEFUNC(int, X509_get_ext_count, X509 *a, a, return 0, return) +DEFINEFUNC4(void *, X509_get_ext_d2i, X509 *a, a, int b, b, int *c, c, int *d, d, return nullptr, return) +DEFINEFUNC(const X509V3_EXT_METHOD *, X509V3_EXT_get, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void *, X509V3_EXT_d2i, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(int, X509_EXTENSION_get_critical, X509_EXTENSION *a, a, return 0, return) +DEFINEFUNC(ASN1_OCTET_STRING *, X509_EXTENSION_get_data, X509_EXTENSION *a, a, return nullptr, return) +DEFINEFUNC(void, BASIC_CONSTRAINTS_free, BASIC_CONSTRAINTS *a, a, return, DUMMYARG) +DEFINEFUNC(void, AUTHORITY_KEYID_free, AUTHORITY_KEYID *a, a, return, DUMMYARG) +DEFINEFUNC(void, GENERAL_NAME_free, GENERAL_NAME *a, a, return, DUMMYARG) +DEFINEFUNC2(int, ASN1_STRING_print, BIO *a, a, const ASN1_STRING *b, b, return 0, return) +DEFINEFUNC2(int, X509_check_issued, X509 *a, a, X509 *b, b, return -1, return) +DEFINEFUNC(X509_NAME *, X509_get_issuer_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(X509_NAME *, X509_get_subject_name, X509 *a, a, return nullptr, return) +DEFINEFUNC(ASN1_INTEGER *, X509_get_serialNumber, X509 *a, a, return nullptr, return) +DEFINEFUNC(int, X509_verify_cert, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_NAME_entry_count, X509_NAME *a, a, return 0, return) +DEFINEFUNC2(X509_NAME_ENTRY *, X509_NAME_get_entry, X509_NAME *a, a, int b, b, return nullptr, return) +DEFINEFUNC(ASN1_STRING *, X509_NAME_ENTRY_get_data, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(ASN1_OBJECT *, X509_NAME_ENTRY_get_object, X509_NAME_ENTRY *a, a, return nullptr, return) +DEFINEFUNC(EVP_PKEY *, X509_PUBKEY_get, X509_PUBKEY *a, a, return nullptr, return) +DEFINEFUNC(void, X509_STORE_free, X509_STORE *a, a, return, DUMMYARG) +DEFINEFUNC(X509_STORE *, X509_STORE_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(int, X509_STORE_add_cert, X509_STORE *a, a, X509 *b, b, return 0, return) +DEFINEFUNC(void, X509_STORE_CTX_free, X509_STORE_CTX *a, a, return, DUMMYARG) +DEFINEFUNC4(int, X509_STORE_CTX_init, X509_STORE_CTX *a, a, X509_STORE *b, b, X509 *c, c, STACK_OF(X509) *d, d, return -1, return) +DEFINEFUNC2(int, X509_STORE_CTX_set_purpose, X509_STORE_CTX *a, a, int b, b, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(int, X509_STORE_CTX_get_error_depth, X509_STORE_CTX *a, a, return -1, return) +DEFINEFUNC(X509 *, X509_STORE_CTX_get_current_cert, X509_STORE_CTX *a, a, return nullptr, return) +DEFINEFUNC(X509_STORE_CTX *, X509_STORE_CTX_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC2(void *, X509_STORE_CTX_get_ex_data, X509_STORE_CTX *ctx, ctx, int idx, idx, return nullptr, return) +DEFINEFUNC(int, SSL_get_ex_data_X509_STORE_CTX_idx, DUMMYARG, DUMMYARG, return -1, return) +DEFINEFUNC3(int, SSL_CTX_load_verify_locations, SSL_CTX *ctx, ctx, const char *CAfile, CAfile, const char *CApath, CApath, return 0, return) +DEFINEFUNC2(int, i2d_SSL_SESSION, SSL_SESSION *in, in, unsigned char **pp, pp, return 0, return) +DEFINEFUNC3(SSL_SESSION *, d2i_SSL_SESSION, SSL_SESSION **a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_NEXTPROTONEG) +DEFINEFUNC6(int, SSL_select_next_proto, unsigned char **out, out, unsigned char *outlen, outlen, + const unsigned char *in, in, unsigned int inlen, inlen, + const unsigned char *client, client, unsigned int client_len, client_len, + return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_next_proto_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_next_proto_negotiated, const SSL *s, s, + const unsigned char **data, data, unsigned *len, len, return, DUMMYARG) +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +DEFINEFUNC3(int, SSL_set_alpn_protos, SSL *s, s, const unsigned char *protos, protos, + unsigned protos_len, protos_len, return -1, return) +DEFINEFUNC3(void, SSL_CTX_set_alpn_select_cb, SSL_CTX *s, s, + int (*cb) (SSL *ssl, const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, void *arg), cb, + void *arg, arg, return, DUMMYARG) +DEFINEFUNC3(void, SSL_get0_alpn_selected, const SSL *s, s, const unsigned char **data, data, + unsigned *len, len, return, DUMMYARG) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ... +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... + +DEFINEFUNC2(void, BIO_set_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void, BIO_clear_flags, BIO *b, b, int flags, flags, return, DUMMYARG) +DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, return) +DEFINEFUNC3(int, BIO_set_ex_data, BIO *b, b, int idx, idx, void *data, data, return -1, return) + +DEFINEFUNC3(void *, CRYPTO_malloc, size_t num, num, const char *file, file, int line, line, return nullptr, return) +DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return) +DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG) +DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return nullptr, return) +DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return) +DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return) +DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return) +#ifndef OPENSSL_NO_EC +DEFINEFUNC(EC_KEY *, EC_KEY_dup, const EC_KEY *ec, ec, return nullptr, return) +DEFINEFUNC(EC_KEY *, EC_KEY_new_by_curve_name, int nid, nid, return nullptr, return) +DEFINEFUNC(void, EC_KEY_free, EC_KEY *ecdh, ecdh, return, DUMMYARG) +DEFINEFUNC2(size_t, EC_get_builtin_curves, EC_builtin_curve * r, r, size_t nitems, nitems, return 0, return) +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +DEFINEFUNC(int, EC_curve_nist2nid, const char *name, name, return 0, return) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // OPENSSL_NO_EC + +DEFINEFUNC5(int, PKCS12_parse, PKCS12 *p12, p12, const char *pass, pass, EVP_PKEY **pkey, pkey, \ + X509 **cert, cert, STACK_OF(X509) **ca, ca, return 1, return); +DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, return nullptr, return); +DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG) + +DEFINEFUNC(X509_REQ*, X509_REQ_new, void, DUMMYARG, return NULL, return); +DEFINEFUNC(void, X509_REQ_free, X509_REQ *req, req, return, return); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L +DEFINEFUNC(X509_NAME*, X509_REQ_get_subject_name, X509_REQ *req, req, return NULL, return); +DEFINEFUNC2(EVP_PKEY_CTX*, EVP_PKEY_CTX_new_id, int id, id, ENGINE *e, e, return NULL, return); +DEFINEFUNC(void, EVP_PKEY_CTX_free, EVP_PKEY_CTX *ctx, ctx, return, return); +DEFINEFUNC(int, EVP_PKEY_keygen_init, EVP_PKEY_CTX *ctx, ctx, return 0, return); +DEFINEFUNC2(int, EVP_PKEY_keygen, EVP_PKEY_CTX *ctx, ctx, EVP_PKEY **pkey, pkey, return 0, return); +DEFINEFUNC5(int, RSA_pkey_ctx_ctrl, EVP_PKEY_CTX *ctx, ctx, int optype, optype, int cmd, cmd, int p1, p1, void *p2, p2, return 0, return) + +#endif +DEFINEFUNC7(int, X509_NAME_add_entry_by_txt, X509_NAME *name, name, const char *field, field, int type, type, const unsigned char *bytes, bytes, int len, len, int loc, loc, int set, set, return 0, return); +DEFINEFUNC7(int, X509_NAME_add_entry_by_OBJ, X509_NAME *name, name, const ASN1_OBJECT *obj, obj, int type, type, const unsigned char *bytes, bytes, int len, len, int loc, loc, int set, set, return 0, return); +DEFINEFUNC2(ASN1_OBJECT *, OBJ_txt2obj, const char *s, s, int no_name, no_name, return NULL, return); +DEFINEFUNC2(int, X509_REQ_set_pubkey, X509_REQ *x, x, EVP_PKEY *pkey, pkey, return 0, return); +DEFINEFUNC3(int, X509_REQ_sign, X509_REQ *x, x, EVP_PKEY *pkey, pkey, const EVP_MD *md, md, return 0, return); +DEFINEFUNC2(int, PEM_write_bio_X509_REQ, BIO *bp, bp, X509_REQ *x, x, return 0, return); +DEFINEFUNC2(int, PEM_write_bio_X509_REQ_NEW, BIO *bp, bp, X509_REQ *x, x, return 0, return); +DEFINEFUNC2(int, X509_REQ_set_version, X509_REQ *x, x, long version, version, return 0, return); +DEFINEFUNC2(int, X509_REQ_add_extensions, X509_REQ *req, req, STACK_OF(X509_EXTENSION) *exts, exts, return 0, return) +DEFINEFUNC(void, X509_EXTENSION_free, X509_EXTENSION *ext, ext, return, return) +DEFINEFUNC2(int, X509_EXTENSION_set_critical, X509_EXTENSION *ex, ex, int crit, crit, return 0, return) +DEFINEFUNC3(X509*, X509_REQ_to_X509, X509_REQ *r, r, int days, days, EVP_PKEY *pkey, pkey, return NULL, return) +DEFINEFUNC2(int, PEM_write_bio_X509, BIO *bp, bp, X509 *x, x, return 0, return) +DEFINEFUNC(X509*, X509_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC2(int, ASN1_INTEGER_set, ASN1_INTEGER *a, a, long v, v, return 0, return) +DEFINEFUNC2(int, X509_set_pubkey,X509 *x, x, EVP_PKEY *key, key, return 0, return) +DEFINEFUNC2(int, X509_set_issuer_name, X509 *x, x, X509_NAME *name, name, return 0, return) +DEFINEFUNC3(int, X509_sign, X509 *x, x, EVP_PKEY *key, key, const EVP_MD *md, md, return 0, return) +DEFINEFUNC3(int, X509_add_ext, X509 *x, x, X509_EXTENSION *ex, ex, int loc, loc, return 0, return) +DEFINEFUNC2(int, X509_set_version, X509 *x, x, long version, version, return 0, return) +DEFINEFUNC2(int, X509_set_subject_name, X509 *x, x, X509_NAME *name, name, return 0, return) +DEFINEFUNC(ASN1_OCTET_STRING *, ASN1_OCTET_STRING_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC4(int, X509_pubkey_digest, const X509 *data, data, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return 0, return) +DEFINEFUNC3(int, ASN1_OCTET_STRING_set, ASN1_OCTET_STRING *str, str, const unsigned char *data, data, int len, len, return 0, return) +DEFINEFUNC5(int, X509_add1_ext_i2d, X509 *x, x, int nid, nid, void *value, value, int crit, crit, unsigned long flags, flags, return 0, return) +DEFINEFUNC(void, ASN1_OCTET_STRING_free, ASN1_OCTET_STRING *a, a, return, return) +DEFINEFUNC(ASN1_INTEGER *, ASN1_INTEGER_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC(GENERAL_NAMES *, GENERAL_NAMES_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC(GENERAL_NAME *, GENERAL_NAME_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC(X509_NAME *, X509_NAME_dup, X509_NAME *xn, xn, return NULL, return) +DEFINEFUNC2(int, X509_set_serialNumber, X509 *x, x, ASN1_INTEGER *serial, serial, return 0, return) +DEFINEFUNC(AUTHORITY_KEYID *, AUTHORITY_KEYID_new, void, DUMMYARG, return NULL, return) +DEFINEFUNC(ASN1_INTEGER *, ASN1_INTEGER_dup, const ASN1_INTEGER *x, x, return NULL, return) +DEFINEFUNC4(int, X509_NAME_digest, const X509_NAME *data, data, const EVP_MD *type, type, unsigned char *md, md, unsigned int *len, len, return 0, return) +DEFINEFUNC(void, ASN1_INTEGER_free, ASN1_INTEGER *a, a, return, return) +DEFINEFUNC2(int, i2d_X509_REQ_bio, BIO *bp, bp, X509_REQ *req, req, return 0, return) +DEFINEFUNC2(int, i2d_X509_bio, BIO *bp, bp, X509 *x509, x509, return 0, return) + +#define RESOLVEFUNC(func) \ + if (!(_q_##func = _q_PTR_##func(libs.ssl->resolve(#func))) \ + && !(_q_##func = _q_PTR_##func(libs.crypto->resolve(#func)))) \ + qsslSocketCannotResolveSymbolWarning(#func); + +#if !defined QT_LINKED_OPENSSL + +#if !QT_CONFIG(library) +bool q_resolveOpenSslSymbols() +{ + qCWarning(lcSsl, "QSslSocket: unable to resolve symbols. Qt is configured without the " + "'library' feature, which means runtime resolving of libraries won't work."); + qCWarning(lcSsl, "Either compile Qt statically or with support for runtime resolving " + "of libraries."); + return false; +} +#else + +# ifdef Q_OS_UNIX +struct NumericallyLess +{ + typedef bool result_type; + result_type operator()(const QStringRef &lhs, const QStringRef &rhs) const + { + bool ok = false; + int b = 0; + int a = lhs.toInt(&ok); + if (ok) + b = rhs.toInt(&ok); + if (ok) { + // both toInt succeeded + return a < b; + } else { + // compare as strings; + return lhs < rhs; + } + } +}; + +struct LibGreaterThan +{ + typedef bool result_type; + result_type operator()(const QString &lhs, const QString &rhs) const + { + const QVector<QStringRef> lhsparts = lhs.splitRef(QLatin1Char('.')); + const QVector<QStringRef> rhsparts = rhs.splitRef(QLatin1Char('.')); + Q_ASSERT(lhsparts.count() > 1 && rhsparts.count() > 1); + + // note: checking rhs < lhs, the same as lhs > rhs + return std::lexicographical_compare(rhsparts.begin() + 1, rhsparts.end(), + lhsparts.begin() + 1, lhsparts.end(), + NumericallyLess()); + } +}; + +#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) +static int dlIterateCallback(struct dl_phdr_info *info, size_t size, void *data) +{ + if (size < sizeof (info->dlpi_addr) + sizeof (info->dlpi_name)) + return 1; + QSet<QString> *paths = (QSet<QString> *)data; + QString path = QString::fromLocal8Bit(info->dlpi_name); + if (!path.isEmpty()) { + QFileInfo fi(path); + path = fi.absolutePath(); + if (!path.isEmpty()) + paths->insert(path); + } + return 0; +} +#endif + +static QStringList libraryPathList() +{ + QStringList paths; +# ifdef Q_OS_DARWIN + paths = QString::fromLatin1(qgetenv("DYLD_LIBRARY_PATH")) + .split(QLatin1Char(':'), QString::SkipEmptyParts); + + // search in .app/Contents/Frameworks + UInt32 packageType; + CFBundleGetPackageInfo(CFBundleGetMainBundle(), &packageType, nullptr); + if (packageType == FOUR_CHAR_CODE('APPL')) { + QUrl bundleUrl = QUrl::fromCFURL(QCFType<CFURLRef>(CFBundleCopyBundleURL(CFBundleGetMainBundle()))); + QUrl frameworksUrl = QUrl::fromCFURL(QCFType<CFURLRef>(CFBundleCopyPrivateFrameworksURL(CFBundleGetMainBundle()))); + paths << bundleUrl.resolved(frameworksUrl).path(); + } +# else + paths = QString::fromLatin1(qgetenv("LD_LIBRARY_PATH")) + .split(QLatin1Char(':'), QString::SkipEmptyParts); +# endif + paths << QLatin1String("/lib") << QLatin1String("/usr/lib") << QLatin1String("/usr/local/lib"); + paths << QLatin1String("/lib64") << QLatin1String("/usr/lib64") << QLatin1String("/usr/local/lib64"); + paths << QLatin1String("/lib32") << QLatin1String("/usr/lib32") << QLatin1String("/usr/local/lib32"); + +#if defined(Q_OS_ANDROID) + paths << QLatin1String("/system/lib"); +#elif defined(Q_OS_LINUX) + // discover paths of already loaded libraries + QSet<QString> loadedPaths; + dl_iterate_phdr(dlIterateCallback, &loadedPaths); + paths.append(loadedPaths.values()); +#endif + + return paths; +} + +Q_NEVER_INLINE +static QStringList findAllLibs(QLatin1String filter) +{ + const QStringList paths = libraryPathList(); + QStringList found; + const QStringList filters((QString(filter))); + + for (const QString &path : paths) { + QDir dir(path); + QStringList entryList = dir.entryList(filters, QDir::Files); + + std::sort(entryList.begin(), entryList.end(), LibGreaterThan()); + for (const QString &entry : qAsConst(entryList)) + found << path + QLatin1Char('/') + entry; + } + + return found; +} + +static QStringList findAllLibSsl() +{ + return findAllLibs(QLatin1String("libssl.*")); +} + +static QStringList findAllLibCrypto() +{ + return findAllLibs(QLatin1String("libcrypto.*")); +} +# endif + +#ifdef Q_OS_WIN + +struct LoadedOpenSsl { + std::unique_ptr<QSystemLibrary> ssl, crypto; +}; + +static bool tryToLoadOpenSslWin32Library(QLatin1String ssleay32LibName, QLatin1String libeay32LibName, LoadedOpenSsl &result) +{ + auto ssleay32 = qt_make_unique<QSystemLibrary>(ssleay32LibName); + if (!ssleay32->load(false)) { + return FALSE; + } + + auto libeay32 = qt_make_unique<QSystemLibrary>(libeay32LibName); + if (!libeay32->load(false)) { + return FALSE; + } + + result.ssl = std::move(ssleay32); + result.crypto = std::move(libeay32); + return TRUE; +} + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result; + +#if QT_CONFIG(opensslv11) + // With OpenSSL 1.1 the names have changed to libssl-1_1(-x64) and libcrypto-1_1(-x64), for builds using + // MSVC and GCC, (-x64 suffix for 64-bit builds). + +#ifdef Q_PROCESSOR_X86_64 +#define QT_SSL_SUFFIX "-x64" +#else // !Q_PROCESSOFR_X86_64 +#define QT_SSL_SUFFIX +#endif // !Q_PROCESSOR_x86_64 + + tryToLoadOpenSslWin32Library(QLatin1String("libssl-1_1" QT_SSL_SUFFIX), + QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), result); + +#undef QT_SSL_SUFFIX + +#else // QT_CONFIG(opensslv11) + + // When OpenSSL is built using MSVC then the libraries are named 'ssleay32.dll' and 'libeay32'dll'. + // When OpenSSL is built using GCC then different library names are used (depending on the OpenSSL version) + // The oldest version of a GCC-based OpenSSL which can be detected by the code below is 0.9.8g (released in 2007) + if (!tryToLoadOpenSslWin32Library(QLatin1String("ssleay32"), QLatin1String("libeay32"), result)) { + if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-10"), QLatin1String("libcrypto-10"), result)) { + if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-8"), QLatin1String("libcrypto-8"), result)) { + tryToLoadOpenSslWin32Library(QLatin1String("libssl-7"), QLatin1String("libcrypto-7"), result); + } + } + } +#endif // !QT_CONFIG(opensslv11) + + return result; +} +#else + +struct LoadedOpenSsl { + std::unique_ptr<QLibrary> ssl, crypto; +}; + +static LoadedOpenSsl loadOpenSsl() +{ + LoadedOpenSsl result = {qt_make_unique<QLibrary>(), qt_make_unique<QLibrary>()}; + +# if defined(Q_OS_UNIX) + QLibrary * const libssl = result.ssl.get(); + QLibrary * const libcrypto = result.crypto.get(); + + // Try to find the libssl library on the system. + // + // Up until Qt 4.3, this only searched for the "ssl" library at version -1, that + // is, libssl.so on most Unix systems. However, the .so file isn't present in + // user installations because it's considered a development file. + // + // The right thing to do is to load the library at the major version we know how + // to work with: the SHLIB_VERSION_NUMBER version (macro defined in opensslv.h) + // + // However, OpenSSL is a well-known case of binary-compatibility breakage. To + // avoid such problems, many system integrators and Linux distributions change + // the soname of the binary, letting the full version number be the soname. So + // we'll find libssl.so.0.9.7, libssl.so.0.9.8, etc. in the system. For that + // reason, we will search a few common paths (see findAllLibSsl() above) in hopes + // we find one that works. + // + // If that fails, for OpenSSL 1.0 we also try some fallbacks -- look up + // libssl.so with a hardcoded soname. The reason is QTBUG-68156: the binary + // builds of Qt happen (at the time of this writing) on RHEL machines, + // which change SHLIB_VERSION_NUMBER to a non-portable string. When running + // those binaries on the target systems, this code won't pick up + // libssl.so.MODIFIED_SHLIB_VERSION_NUMBER because it doesn't exist there. + // Given that the only 1.0 supported release (at the time of this writing) + // is 1.0.2, with soname "1.0.0", give that a try too. Note that we mandate + // OpenSSL >= 1.0.0 with a configure-time check, and OpenSSL has kept binary + // compatibility between 1.0.0 and 1.0.2. + // + // It is important, however, to try the canonical name and the unversioned name + // without going through the loop. By not specifying a path, we let the system + // dlopen(3) function determine it for us. This will include any DT_RUNPATH or + // DT_RPATH tags on our library header as well as other system-specific search + // paths. See the man page for dlopen(3) on your system for more information. + +#ifdef Q_OS_OPENBSD + libcrypto->setLoadHints(QLibrary::ExportExternalSymbolsHint); +#endif +#if defined(SHLIB_VERSION_NUMBER) && !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so + // first attempt: the canonical name is libssl.so.<SHLIB_VERSION_NUMBER> + libssl->setFileNameAndVersion(QLatin1String("ssl"), QLatin1String(SHLIB_VERSION_NUMBER)); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), QLatin1String(SHLIB_VERSION_NUMBER)); + if (libcrypto->load() && libssl->load()) { + // libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } + +#if !QT_CONFIG(opensslv11) + // first-and-half attempts: for OpenSSL 1.0 try to load some hardcoded sonames: + // - "1.0.0" is the official upstream one + // - "1.0.2" is found on some distributions (e.g. Debian) that patch OpenSSL + static const QLatin1String fallbackSonames[] = { + QLatin1String("1.0.0"), + QLatin1String("1.0.2") + }; + + for (auto fallbackSoname : fallbackSonames) { + libssl->setFileNameAndVersion(QLatin1String("ssl"), fallbackSoname); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), fallbackSoname); + if (libcrypto->load() && libssl->load()) { + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } + } +#endif +#endif + +#ifndef Q_OS_DARWIN + // second attempt: find the development files libssl.so and libcrypto.so + // + // disabled on macOS/iOS: + // macOS's /usr/lib/libssl.dylib, /usr/lib/libcrypto.dylib will be picked up in the third + // attempt, _after_ <bundle>/Contents/Frameworks has been searched. + // iOS does not ship a system libssl.dylib, libcrypto.dylib in the first place. +# if defined(Q_OS_ANDROID) + // OpenSSL 1.1.x must be suffixed otherwise it will use the system libcrypto.so libssl.so which on API-21 are OpenSSL 1.0 not 1.1 + auto openSSLSuffix = [](const QByteArray &defaultSuffix = {}) { + auto suffix = qgetenv("ANDROID_OPENSSL_SUFFIX"); + if (suffix.isEmpty()) + return defaultSuffix; + return suffix; + }; +# if QT_CONFIG(opensslv11) + static QString suffix = QString::fromLatin1(openSSLSuffix("_1_1")); +# else + static QString suffix = QString::fromLatin1(openSSLSuffix()); +# endif + libssl->setFileNameAndVersion(QLatin1String("ssl") + suffix, -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto") + suffix, -1); +# else + libssl->setFileNameAndVersion(QLatin1String("ssl"), -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto"), -1); +# endif + if (libcrypto->load() && libssl->load()) { + // libssl.so.0 and libcrypto.so.0 found + return result; + } else { + libssl->unload(); + libcrypto->unload(); + } +#endif + + // third attempt: loop on the most common library paths and find libssl + const QStringList sslList = findAllLibSsl(); + const QStringList cryptoList = findAllLibCrypto(); + + for (const QString &crypto : cryptoList) { + libcrypto->setFileNameAndVersion(crypto, -1); + if (libcrypto->load()) { + QFileInfo fi(crypto); + QString version = fi.completeSuffix(); + + for (const QString &ssl : sslList) { + if (!ssl.endsWith(version)) + continue; + + libssl->setFileNameAndVersion(ssl, -1); + + if (libssl->load()) { + // libssl.so.x and libcrypto.so.x found + return result; + } else { + libssl->unload(); + } + } + } + libcrypto->unload(); + } + + // failed to load anything + result = {}; + return result; + +# else + // not implemented for this platform yet + return result; +# endif +} +#endif + +static QBasicMutex symbolResolveMutex; +static QBasicAtomicInt symbolsResolved = Q_BASIC_ATOMIC_INITIALIZER(false); +static bool triedToResolveSymbols = false; + +bool q_resolveOpenSslSymbols() +{ + if (symbolsResolved.loadAcquire()) + return true; + QMutexLocker locker(&symbolResolveMutex); + if (symbolsResolved.loadRelaxed()) + return true; + if (triedToResolveSymbols) + return false; + triedToResolveSymbols = true; + + LoadedOpenSsl libs = loadOpenSsl(); + if (!libs.ssl || !libs.crypto) + // failed to load them + return false; + +#if QT_CONFIG(opensslv11) + RESOLVEFUNC(X509_REQ_get_subject_name) // v1.1.0 + RESOLVEFUNC(OPENSSL_init_ssl) + RESOLVEFUNC(OPENSSL_init_crypto) + RESOLVEFUNC(ASN1_STRING_get0_data) + RESOLVEFUNC(EVP_CIPHER_CTX_reset) + RESOLVEFUNC(EVP_PKEY_base_id) + RESOLVEFUNC(RSA_bits) + RESOLVEFUNC(OPENSSL_sk_new_null) + RESOLVEFUNC(OPENSSL_sk_push) + RESOLVEFUNC(OPENSSL_sk_free) + RESOLVEFUNC(OPENSSL_sk_num) + RESOLVEFUNC(OPENSSL_sk_pop_free) + RESOLVEFUNC(OPENSSL_sk_value) + RESOLVEFUNC(DH_get0_pqg) + RESOLVEFUNC(CRYPTO_get_ex_new_index) + RESOLVEFUNC(X509_up_ref) + RESOLVEFUNC(X509_STORE_CTX_get0_chain) + RESOLVEFUNC(X509_getm_notBefore) + RESOLVEFUNC(X509_getm_notAfter) + RESOLVEFUNC(X509_get_version) + RESOLVEFUNC(X509_get_pubkey) + RESOLVEFUNC(X509_STORE_set_verify_cb) + RESOLVEFUNC(CRYPTO_free) + RESOLVEFUNC(OpenSSL_version_num) + RESOLVEFUNC(OpenSSL_version) + if (!_q_OpenSSL_version) { + // Apparently, we were built with OpenSSL 1.1 enabled but are now using + // a wrong library. + qCWarning(lcSsl, "Incompatible version of OpenSSL"); + return false; + } + + RESOLVEFUNC(DH_bits) + + RESOLVEFUNC(BIO_set_data) + RESOLVEFUNC(BIO_get_data) + RESOLVEFUNC(BIO_set_init) + RESOLVEFUNC(BIO_get_shutdown) + RESOLVEFUNC(BIO_set_shutdown) + RESOLVEFUNC(EVP_PKEY_CTX_new_id) + RESOLVEFUNC(EVP_PKEY_CTX_free) + RESOLVEFUNC(EVP_PKEY_keygen_init) + RESOLVEFUNC(EVP_PKEY_keygen); + RESOLVEFUNC(RSA_pkey_ctx_ctrl); + RESOLVEFUNC(BIO_free_all) + RESOLVEFUNC(X509_REQ_new) + RESOLVEFUNC(X509_REQ_set_version) + RESOLVEFUNC(OBJ_txt2obj) + RESOLVEFUNC(X509_NAME_add_entry_by_OBJ) + RESOLVEFUNC(X509_REQ_add_extensions) + RESOLVEFUNC(X509_EXTENSION_free) + RESOLVEFUNC(X509_REQ_set_pubkey) + RESOLVEFUNC(X509_REQ_sign) + RESOLVEFUNC(PEM_write_bio_X509_REQ) + RESOLVEFUNC(X509_REQ_free) + +#else // !opensslv11 + + RESOLVEFUNC(ASN1_STRING_data) + +#ifdef SSLEAY_MACROS + RESOLVEFUNC(ASN1_dup) +#endif // SSLEAY_MACROS + RESOLVEFUNC(BIO_new_file) + RESOLVEFUNC(ERR_clear_error) + RESOLVEFUNC(ERR_load_crypto_strings) + RESOLVEFUNC(CRYPTO_free) + RESOLVEFUNC(CRYPTO_num_locks) + RESOLVEFUNC(CRYPTO_set_id_callback) + RESOLVEFUNC(CRYPTO_set_locking_callback) + RESOLVEFUNC(ERR_peek_last_error) + RESOLVEFUNC(ERR_free_strings) + RESOLVEFUNC(EVP_CIPHER_CTX_cleanup) + RESOLVEFUNC(EVP_CIPHER_CTX_init) + +#ifdef SSLEAY_MACROS // ### verify + RESOLVEFUNC(PEM_ASN1_read_bio) +#endif // SSLEAY_MACROS + + RESOLVEFUNC(sk_new_null) + RESOLVEFUNC(sk_push) + RESOLVEFUNC(sk_free) + RESOLVEFUNC(sk_num) + RESOLVEFUNC(sk_pop_free) + RESOLVEFUNC(sk_value) + RESOLVEFUNC(X509_STORE_CTX_get_chain) +#ifdef SSLEAY_MACROS + RESOLVEFUNC(i2d_RSAPrivateKey) + RESOLVEFUNC(d2i_RSAPrivateKey) +#endif + + RESOLVEFUNC(CONF_get1_default_config_file) + RESOLVEFUNC(OPENSSL_add_all_algorithms_noconf) + RESOLVEFUNC(OPENSSL_add_all_algorithms_conf) + RESOLVEFUNC(SSLeay) + RESOLVEFUNC(X509_REQ_new) + RESOLVEFUNC(X509_REQ_free) + RESOLVEFUNC(X509_NAME_add_entry_by_txt) + RESOLVEFUNC(X509_NAME_add_entry_by_OBJ) + RESOLVEFUNC(OBJ_txt2obj) + RESOLVEFUNC(X509_REQ_add_extensions) + + RESOLVEFUNC(X509_REQ_set_pubkey) + RESOLVEFUNC(X509_REQ_sign) + RESOLVEFUNC(PEM_write_bio_X509_REQ) + RESOLVEFUNC(PEM_write_bio_X509_REQ_NEW) + RESOLVEFUNC(X509_REQ_set_version) + RESOLVEFUNC(BIO_free_all) + RESOLVEFUNC(X509_EXTENSION_free) + + if (!_q_SSLeay || q_SSLeay() >= 0x10100000L) { + // OpenSSL 1.1 has deprecated and removed SSLeay. We consider a failure to + // resolve this symbol as a failure to resolve symbols. + // The right operand of '||' above is ... a bit of paranoia. + qCWarning(lcSsl, "Incompatible version of OpenSSL"); + return false; + } + + + RESOLVEFUNC(SSLeay_version) + +#ifndef OPENSSL_NO_EC +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + if (q_SSLeay() >= 0x10002000L) + RESOLVEFUNC(EC_curve_nist2nid) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // OPENSSL_NO_EC + + +#endif // !opensslv11 + + RESOLVEFUNC(ASN1_INTEGER_get) + RESOLVEFUNC(ASN1_INTEGER_cmp) + RESOLVEFUNC(ASN1_STRING_length) + RESOLVEFUNC(ASN1_STRING_to_UTF8) + RESOLVEFUNC(BIO_ctrl) + RESOLVEFUNC(BIO_free) + RESOLVEFUNC(BIO_new) + RESOLVEFUNC(BIO_new_mem_buf) + RESOLVEFUNC(BIO_read) + RESOLVEFUNC(BIO_s_mem) + RESOLVEFUNC(BIO_write) + RESOLVEFUNC(BIO_set_flags) + RESOLVEFUNC(BIO_clear_flags) + RESOLVEFUNC(BIO_set_ex_data) + RESOLVEFUNC(BIO_get_ex_data) + RESOLVEFUNC(X509V3_EXT_conf_nid) + RESOLVEFUNC(X509_EXTENSION_set_critical) + +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EC_KEY_get0_group) + RESOLVEFUNC(EC_GROUP_get_degree) +#endif + RESOLVEFUNC(BN_num_bits) +#if QT_CONFIG(opensslv11) + RESOLVEFUNC(BN_is_word) +#endif + RESOLVEFUNC(BN_mod_word) + RESOLVEFUNC(BN_set_word) + RESOLVEFUNC(BN_new) + RESOLVEFUNC(BN_free) + RESOLVEFUNC(BN_clear) + RESOLVEFUNC(BN_clear_free) + RESOLVEFUNC(ERR_error_string) + RESOLVEFUNC(ERR_error_string_n) + RESOLVEFUNC(ERR_get_error) + RESOLVEFUNC(EVP_CIPHER_CTX_new) + RESOLVEFUNC(EVP_CIPHER_CTX_free) + RESOLVEFUNC(EVP_CIPHER_CTX_ctrl) + RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length) + RESOLVEFUNC(EVP_CipherInit) + RESOLVEFUNC(EVP_CipherInit_ex) + RESOLVEFUNC(EVP_CipherUpdate) + RESOLVEFUNC(EVP_CipherFinal) + RESOLVEFUNC(EVP_get_digestbyname) + RESOLVEFUNC(EVP_sha1) + RESOLVEFUNC(EVP_sha256) + RESOLVEFUNC(EVP_aes_256_gcm) + RESOLVEFUNC(EVP_aes_128_cbc) + RESOLVEFUNC(EVP_PKEY_assign) + RESOLVEFUNC(EVP_PKEY_set1_RSA) + RESOLVEFUNC(EVP_PKEY_set1_DH) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EVP_PKEY_set1_EC_KEY) +#endif + RESOLVEFUNC(EVP_PKEY_free) + RESOLVEFUNC(EVP_PKEY_get1_RSA) + RESOLVEFUNC(EVP_PKEY_get1_DH) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EVP_PKEY_get1_EC_KEY) +#endif + RESOLVEFUNC(EVP_PKEY_new) + RESOLVEFUNC(EVP_PKEY_type) + RESOLVEFUNC(OBJ_nid2sn) + RESOLVEFUNC(OBJ_nid2ln) + RESOLVEFUNC(OBJ_sn2nid) + RESOLVEFUNC(OBJ_ln2nid) + RESOLVEFUNC(i2t_ASN1_OBJECT) + RESOLVEFUNC(OBJ_obj2txt) + RESOLVEFUNC(OBJ_obj2nid) + +#ifndef SSLEAY_MACROS + RESOLVEFUNC(PEM_read_bio_PrivateKey) + RESOLVEFUNC(PEM_write_bio_PKCS8PrivateKey) + RESOLVEFUNC(PEM_read_bio_RSAPrivateKey) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(PEM_read_bio_ECPrivateKey) +#endif + RESOLVEFUNC(PEM_read_bio_DHparams) + RESOLVEFUNC(PEM_write_bio_RSAPrivateKey) + RESOLVEFUNC(PEM_write_bio_PrivateKey) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(PEM_write_bio_ECPrivateKey) +#endif +#endif // !SSLEAY_MACROS + + RESOLVEFUNC(PEM_read_bio_PUBKEY) + RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(PEM_read_bio_EC_PUBKEY) +#endif + RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY) + RESOLVEFUNC(PEM_write_bio_PUBKEY) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(PEM_write_bio_EC_PUBKEY) +#endif + RESOLVEFUNC(RAND_seed) + RESOLVEFUNC(RAND_status) + RESOLVEFUNC(RAND_bytes) + RESOLVEFUNC(RSA_new) + RESOLVEFUNC(RSA_generate_key_ex); + RESOLVEFUNC(RSA_free) + RESOLVEFUNC(X509_NAME_entry_count) + RESOLVEFUNC(X509_NAME_get_entry) + RESOLVEFUNC(X509_NAME_ENTRY_get_data) + RESOLVEFUNC(X509_NAME_ENTRY_get_object) + RESOLVEFUNC(X509_PUBKEY_get) + RESOLVEFUNC(X509_STORE_free) + RESOLVEFUNC(X509_STORE_new) + RESOLVEFUNC(X509_STORE_add_cert) + RESOLVEFUNC(X509_STORE_CTX_free) + RESOLVEFUNC(X509_STORE_CTX_init) + RESOLVEFUNC(X509_STORE_CTX_new) + RESOLVEFUNC(X509_STORE_CTX_set_purpose) + RESOLVEFUNC(X509_STORE_CTX_get_error) + RESOLVEFUNC(X509_STORE_CTX_get_error_depth) + RESOLVEFUNC(X509_STORE_CTX_get_current_cert) + RESOLVEFUNC(X509_cmp) + RESOLVEFUNC(X509_STORE_CTX_get_ex_data) + +#ifndef SSLEAY_MACROS + RESOLVEFUNC(X509_dup) +#endif + RESOLVEFUNC(X509_print) + RESOLVEFUNC(X509_digest) + RESOLVEFUNC(X509_EXTENSION_get_object) + RESOLVEFUNC(X509_free) + RESOLVEFUNC(X509_gmtime_adj) + RESOLVEFUNC(ASN1_TIME_free) + RESOLVEFUNC(X509_get_ext) + RESOLVEFUNC(X509_get_ext_count) + RESOLVEFUNC(X509_get_ext_d2i) + RESOLVEFUNC(X509V3_EXT_get) + RESOLVEFUNC(X509V3_EXT_d2i) + RESOLVEFUNC(X509_EXTENSION_get_critical) + RESOLVEFUNC(X509_EXTENSION_get_data) + RESOLVEFUNC(BASIC_CONSTRAINTS_free) + RESOLVEFUNC(AUTHORITY_KEYID_free) + RESOLVEFUNC(GENERAL_NAME_free) + RESOLVEFUNC(ASN1_STRING_print) + RESOLVEFUNC(X509_check_issued) + RESOLVEFUNC(X509_get_issuer_name) + RESOLVEFUNC(X509_get_subject_name) + RESOLVEFUNC(X509_get_serialNumber) + RESOLVEFUNC(X509_verify_cert) + RESOLVEFUNC(d2i_X509) + RESOLVEFUNC(i2d_X509) + RESOLVEFUNC(SSL_CTX_load_verify_locations) + RESOLVEFUNC(i2d_SSL_SESSION) + RESOLVEFUNC(d2i_SSL_SESSION) +#if OPENSSL_VERSION_NUMBER >= 0x1000100fL && !defined(OPENSSL_NO_NEXTPROTONEG) + RESOLVEFUNC(SSL_select_next_proto) + RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb) + RESOLVEFUNC(SSL_get0_next_proto_negotiated) +#endif // OPENSSL_VERSION_NUMBER >= 0x1000100fL ... +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + RESOLVEFUNC(SSL_set_alpn_protos) + RESOLVEFUNC(SSL_CTX_set_alpn_select_cb) + RESOLVEFUNC(SSL_get0_alpn_selected) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L ... + RESOLVEFUNC(CRYPTO_malloc) + RESOLVEFUNC(DH_new) + RESOLVEFUNC(DH_free) + RESOLVEFUNC(d2i_DHparams) + RESOLVEFUNC(i2d_DHparams) + RESOLVEFUNC(DH_check) + RESOLVEFUNC(BN_bin2bn) +#ifndef OPENSSL_NO_EC + RESOLVEFUNC(EC_KEY_dup) + RESOLVEFUNC(EC_KEY_new_by_curve_name) + RESOLVEFUNC(EC_KEY_free) + RESOLVEFUNC(EC_get_builtin_curves) +#endif // OPENSSL_NO_EC + RESOLVEFUNC(PKCS12_parse) + RESOLVEFUNC(d2i_PKCS12_bio) + RESOLVEFUNC(PKCS12_free) + RESOLVEFUNC(X509_REQ_to_X509) + RESOLVEFUNC(PEM_write_bio_X509) + RESOLVEFUNC(X509_new) + RESOLVEFUNC(ASN1_INTEGER_set) + RESOLVEFUNC(X509_set_pubkey) + RESOLVEFUNC(X509_set_issuer_name) + RESOLVEFUNC(X509_sign) + RESOLVEFUNC(X509_add_ext) + RESOLVEFUNC(X509_set_version) + RESOLVEFUNC(X509_set_subject_name) + RESOLVEFUNC(ASN1_OCTET_STRING_new) + RESOLVEFUNC(X509_pubkey_digest) + RESOLVEFUNC(ASN1_OCTET_STRING_set) + RESOLVEFUNC(X509_add1_ext_i2d) + RESOLVEFUNC(ASN1_OCTET_STRING_free) + RESOLVEFUNC(ASN1_INTEGER_new) + RESOLVEFUNC(GENERAL_NAMES_new) + RESOLVEFUNC(GENERAL_NAME_new) + RESOLVEFUNC(X509_NAME_dup) + RESOLVEFUNC(X509_set_serialNumber) + RESOLVEFUNC(AUTHORITY_KEYID_new) + RESOLVEFUNC(ASN1_INTEGER_dup) + RESOLVEFUNC(X509_NAME_digest) + RESOLVEFUNC(ASN1_INTEGER_free) + RESOLVEFUNC(i2d_X509_REQ_bio) + RESOLVEFUNC(i2d_X509_bio) + + symbolsResolved.storeRelease(true); + return true; +} +#endif // QT_CONFIG(library) + +#else // !defined QT_LINKED_OPENSSL + +bool q_resolveOpenSslSymbols() +{ +#ifdef QT_NO_OPENSSL + return false; +#endif + return true; +} +#endif // !defined QT_LINKED_OPENSSL + +//============================================================================== +// contributed by Jay Case of Sarvega, Inc.; http://sarvega.com/ +// Based on X509_cmp_time() for intitial buffer hacking. +//============================================================================== +QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime) +{ + size_t lTimeLength = aTime->length; + char *pString = (char *) aTime->data; + + if (aTime->type == V_ASN1_UTCTIME) { + + char lBuffer[24]; + char *pBuffer = lBuffer; + + if ((lTimeLength < 11) || (lTimeLength > 17)) + return QDateTime(); + + memcpy(pBuffer, pString, 10); + pBuffer += 10; + pString += 10; + + if ((*pString == 'Z') || (*pString == '-') || (*pString == '+')) { + *pBuffer++ = '0'; + *pBuffer++ = '0'; + } else { + *pBuffer++ = *pString++; + *pBuffer++ = *pString++; + // Skip any fractional seconds... + if (*pString == '.') { + pString++; + while ((*pString >= '0') && (*pString <= '9')) + pString++; + } + } + + *pBuffer++ = 'Z'; + *pBuffer++ = '\0'; + + time_t lSecondsFromUCT; + if (*pString == 'Z') { + lSecondsFromUCT = 0; + } else { + if ((*pString != '+') && (*pString != '-')) + return QDateTime(); + + lSecondsFromUCT = ((pString[1] - '0') * 10 + (pString[2] - '0')) * 60; + lSecondsFromUCT += (pString[3] - '0') * 10 + (pString[4] - '0'); + lSecondsFromUCT *= 60; + if (*pString == '-') + lSecondsFromUCT = -lSecondsFromUCT; + } + + tm lTime; + lTime.tm_sec = ((lBuffer[10] - '0') * 10) + (lBuffer[11] - '0'); + lTime.tm_min = ((lBuffer[8] - '0') * 10) + (lBuffer[9] - '0'); + lTime.tm_hour = ((lBuffer[6] - '0') * 10) + (lBuffer[7] - '0'); + lTime.tm_mday = ((lBuffer[4] - '0') * 10) + (lBuffer[5] - '0'); + lTime.tm_mon = (((lBuffer[2] - '0') * 10) + (lBuffer[3] - '0')) - 1; + lTime.tm_year = ((lBuffer[0] - '0') * 10) + (lBuffer[1] - '0'); + if (lTime.tm_year < 50) + lTime.tm_year += 100; // RFC 2459 + + QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + + QDateTime result(resDate, resTime, Qt::UTC); + result = result.addSecs(lSecondsFromUCT); + return result; + + } else if (aTime->type == V_ASN1_GENERALIZEDTIME) { + + if (lTimeLength < 15) + return QDateTime(); // hopefully never triggered + + // generalized time is always YYYYMMDDHHMMSSZ (RFC 2459, section 4.1.2.5.2) + tm lTime; + lTime.tm_sec = ((pString[12] - '0') * 10) + (pString[13] - '0'); + lTime.tm_min = ((pString[10] - '0') * 10) + (pString[11] - '0'); + lTime.tm_hour = ((pString[8] - '0') * 10) + (pString[9] - '0'); + lTime.tm_mday = ((pString[6] - '0') * 10) + (pString[7] - '0'); + lTime.tm_mon = (((pString[4] - '0') * 10) + (pString[5] - '0')); + lTime.tm_year = ((pString[0] - '0') * 1000) + ((pString[1] - '0') * 100) + + ((pString[2] - '0') * 10) + (pString[3] - '0'); + + QDate resDate(lTime.tm_year, lTime.tm_mon, lTime.tm_mday); + QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec); + + QDateTime result(resDate, resTime, Qt::UTC); + return result; + + } else { + qCWarning(lcSsl, "unsupported date format detected"); + return QDateTime(); + } + +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/openssl_symbols_p.h b/src/opcua/x509/openssl_symbols_p.h new file mode 100644 index 0000000..0241c51 --- /dev/null +++ b/src/opcua/x509/openssl_symbols_p.h @@ -0,0 +1,566 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef OPENSSL_SYMBOLS_P_H +#define OPENSSL_SYMBOLS_P_H + +#include <openssl/asn1.h> +#include <openssl/bio.h> +#include <openssl/bn.h> +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/pem.h> +#include <openssl/pkcs12.h> +#include <openssl/pkcs7.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> +#include <openssl/stack.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> +#include <openssl/dsa.h> +#include <openssl/rsa.h> +#include <openssl/crypto.h> +#include <openssl/tls1.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtNetwork/private/qtnetworkglobal_p.h> +#include <QtCore/QLoggingCategory> + +#ifdef Q_OS_WIN +#include <qt_windows.h> +#if defined(X509_NAME) +#undef X509_NAME +#endif +#endif // Q_OS_WIN + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcSsl) + +#define DUMMYARG + +#if !defined QT_LINKED_OPENSSL +// **************** Shared declarations ****************** +// ret func(arg) + +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a); \ + } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func);\ + err; \ + } \ + funcret _q_##func(a, b); \ + } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c); \ + } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g); \ + } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + static _q_PTR_##func _q_##func = 0; \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { \ + if (Q_UNLIKELY(!_q_##func)) { \ + qsslSocketUnresolvedSymbolWarning(#func); \ + err; \ + } \ + funcret _q_##func(a, b, c, d, e, f, g, h, i); \ + } +// **************** Shared declarations ****************** + +#else // !defined QT_LINKED_OPENSSL + +// **************** Static declarations ****************** + +// ret func(arg) +# define DEFINEFUNC(ret, func, arg, a, err, funcret) \ + ret q_##func(arg) { funcret func(a); } + +// ret func(arg1, arg2) +# define DEFINEFUNC2(ret, func, arg1, a, arg2, b, err, funcret) \ + ret q_##func(arg1, arg2) { funcret func(a, b); } + +// ret func(arg1, arg2, arg3) +# define DEFINEFUNC3(ret, func, arg1, a, arg2, b, arg3, c, err, funcret) \ + ret q_##func(arg1, arg2, arg3) { funcret func(a, b, c); } + +// ret func(arg1, arg2, arg3, arg4) +# define DEFINEFUNC4(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4) { funcret func(a, b, c, d); } + +// ret func(arg1, arg2, arg3, arg4, arg5) +# define DEFINEFUNC5(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5) { funcret func(a, b, c, d, e); } + +// ret func(arg1, arg2, arg3, arg4, arg6) +# define DEFINEFUNC6(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6) { funcret func(a, b, c, d, e, f); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7) +# define DEFINEFUNC7(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7) { funcret func(a, b, c, d, e, f, g); } + +// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8, arg9) +# define DEFINEFUNC9(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, arg9, i, err, funcret) \ + ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) { funcret func(a, b, c, d, e, f, g, h, i); } + +// **************** Static declarations ****************** + +#endif // !defined QT_LINKED_OPENSSL + +#if QT_CONFIG(opensslv11) +#include "qsslsocket_openssl11_symbols_p.h" +#else +#include "qsslsocket_opensslpre11_symbols_p.h" +#endif // QT_CONFIG + +bool q_resolveOpenSslSymbols(); +long q_ASN1_INTEGER_get(ASN1_INTEGER *a); +int q_ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); +int q_ASN1_STRING_length(ASN1_STRING *a); +int q_ASN1_STRING_to_UTF8(unsigned char **a, ASN1_STRING *b); +long q_BIO_ctrl(BIO *a, int b, long c, void *d); +Q_AUTOTEST_EXPORT int q_BIO_free(BIO *a); +void q_BIO_free_all(BIO *a); +BIO *q_BIO_new_mem_buf(void *a, int b); +int q_BIO_read(BIO *a, void *b, int c); +Q_AUTOTEST_EXPORT int q_BIO_write(BIO *a, const void *b, int c); +int q_BN_num_bits(const BIGNUM *a); + +#if QT_CONFIG(opensslv11) +int q_BN_is_word(BIGNUM *a, BN_ULONG w); +#else // opensslv11 +// BN_is_word is implemented purely as a +// macro in OpenSSL < 1.1. It doesn't +// call any functions. +// +// The implementation of BN_is_word is +// 100% the same between 1.0.0, 1.0.1 +// and 1.0.2. +// +// Users are required to include <openssl/bn.h>. +#define q_BN_is_word BN_is_word +#endif // !opensslv11 + +BN_ULONG q_BN_mod_word(const BIGNUM *a, BN_ULONG w); +int q_BN_set_word(const BIGNUM *a, BN_ULONG w); +BIGNUM *q_BN_new(); +void q_BN_clear(BIGNUM *a); +void q_BN_free(BIGNUM *a); +void q_BN_clear_free(BIGNUM *a); + +#ifndef OPENSSL_NO_EC +const EC_GROUP* q_EC_KEY_get0_group(const EC_KEY* k); +int q_EC_GROUP_get_degree(const EC_GROUP* g); +#endif +DSA *q_DSA_new(); +void q_DSA_free(DSA *a); +X509 *q_d2i_X509(X509 **a, const unsigned char **b, long c); +char *q_ERR_error_string(unsigned long a, char *b); +void q_ERR_error_string_n(unsigned long e, char *buf, size_t len); +unsigned long q_ERR_get_error(); +EVP_CIPHER_CTX *q_EVP_CIPHER_CTX_new(); +void q_EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a); +int q_EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); +int q_EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); +int q_EVP_CipherInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv, int enc); +int q_EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, const unsigned char *in, int inl); +int q_EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); +const EVP_MD *q_EVP_get_digestbyname(const char *name); +X509_REQ *q_X509_REQ_new(); +void q_X509_REQ_free(X509_REQ *req); +int q_PEM_write_bio_X509_REQ(BIO *bp, X509_REQ *x); +int q_PEM_write_bio_X509_REQ_NEW(BIO *bp, X509_REQ *x); +int q_X509_REQ_add_extensions(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts); +void q_X509_EXTENSION_free(X509_EXTENSION *ext); +int q_X509_EXTENSION_set_critical(X509_EXTENSION *ex, int crit); +ASN1_INTEGER *q_ASN1_INTEGER_dup(const ASN1_INTEGER *x); + +#if QT_CONFIG(opensslv11) +X509_NAME *q_X509_REQ_get_subject_name(X509_REQ *req); +#define q_ERR_load_crypto_strings() \ + q_OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) +#else // opensslv11 +void q_ERR_load_crypto_strings(); +#define q_X509_REQ_get_subject_name X509_REQ_get_subject_name +#define q_X509_get_notBefore X509_get_notBefore +#define q_X509_get_notAfter X509_get_notAfter +#endif // !opensslv11 + + +int q_X509_NAME_add_entry_by_txt(X509_NAME *name, const char *field, int type, const unsigned char *bytes, int len, int loc, int set); +int q_X509_NAME_add_entry_by_OBJ(X509_NAME *name, const ASN1_OBJECT *obj, int type, const unsigned char *bytes, int len, int loc, int set); +ASN1_OBJECT *q_OBJ_txt2obj(const char *s, int no_name); +int q_X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey); +int q_X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md); +int q_X509_REQ_set_version(X509_REQ *x, long version); +X509_EXTENSION *q_X509V3_EXT_conf_nid(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, int ext_nid, char *value); + +#ifndef OPENSSL_NO_DES +const EVP_CIPHER *q_EVP_des_cbc(); +const EVP_CIPHER *q_EVP_des_ede3_cbc(); +#endif +const EVP_CIPHER *q_EVP_aes_256_gcm(); +const EVP_CIPHER *q_EVP_aes_128_cbc(); +Q_AUTOTEST_EXPORT const EVP_MD *q_EVP_sha1(); +const EVP_MD *q_EVP_sha256(); +int q_EVP_PKEY_assign(EVP_PKEY *a, int b, char *c); +Q_AUTOTEST_EXPORT int q_EVP_PKEY_set1_RSA(EVP_PKEY *a, RSA *b); +int q_EVP_PKEY_set1_DSA(EVP_PKEY *a, DSA *b); +int q_EVP_PKEY_set1_DH(EVP_PKEY *a, DH *b); +#ifndef OPENSSL_NO_EC +int q_EVP_PKEY_set1_EC_KEY(EVP_PKEY *a, EC_KEY *b); +#endif +Q_AUTOTEST_EXPORT void q_EVP_PKEY_free(EVP_PKEY *a); +RSA *q_EVP_PKEY_get1_RSA(EVP_PKEY *a); +DSA *q_EVP_PKEY_get1_DSA(EVP_PKEY *a); +DH *q_EVP_PKEY_get1_DH(EVP_PKEY *a); +#ifndef OPENSSL_NO_EC +EC_KEY *q_EVP_PKEY_get1_EC_KEY(EVP_PKEY *a); +#endif +int q_EVP_PKEY_type(int a); +Q_AUTOTEST_EXPORT EVP_PKEY *q_EVP_PKEY_new(); +int q_i2d_X509(X509 *a, unsigned char **b); +const char *q_OBJ_nid2sn(int a); +const char *q_OBJ_nid2ln(int a); +int q_OBJ_sn2nid(const char *s); +int q_OBJ_ln2nid(const char *s); +int q_i2t_ASN1_OBJECT(char *buf, int buf_len, ASN1_OBJECT *obj); +int q_OBJ_obj2txt(char *buf, int buf_len, ASN1_OBJECT *obj, int no_name); +int q_OBJ_obj2nid(const ASN1_OBJECT *a); +#define q_EVP_get_digestbynid(a) q_EVP_get_digestbyname(q_OBJ_nid2sn(a)) +#ifdef SSLEAY_MACROS +// ### verify +void *q_PEM_ASN1_read_bio(d2i_of_void *a, const char *b, BIO *c, void **d, pem_password_cb *e, + void *f); +// ### ditto for write +#else +Q_AUTOTEST_EXPORT EVP_PKEY *q_PEM_read_bio_PrivateKey(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); +DSA *q_PEM_read_bio_DSAPrivateKey(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSAPrivateKey(BIO *a, RSA **b, pem_password_cb *c, void *d); +#ifndef OPENSSL_NO_EC +EC_KEY *q_PEM_read_bio_ECPrivateKey(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); +#endif +DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d); +int q_PEM_write_bio_DSAPrivateKey(BIO *a, DSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_RSAPrivateKey(BIO *a, RSA *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_PrivateKey(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +int q_PEM_write_bio_PKCS8PrivateKey(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +#ifndef OPENSSL_NO_EC +int q_PEM_write_bio_ECPrivateKey(BIO *a, EC_KEY *b, const EVP_CIPHER *c, unsigned char *d, + int e, pem_password_cb *f, void *g); +#endif +#endif // SSLEAY_MACROS +Q_AUTOTEST_EXPORT EVP_PKEY *q_PEM_read_bio_PUBKEY(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d); +DSA *q_PEM_read_bio_DSA_PUBKEY(BIO *a, DSA **b, pem_password_cb *c, void *d); +RSA *q_PEM_read_bio_RSA_PUBKEY(BIO *a, RSA **b, pem_password_cb *c, void *d); +#ifndef OPENSSL_NO_EC +EC_KEY *q_PEM_read_bio_EC_PUBKEY(BIO *a, EC_KEY **b, pem_password_cb *c, void *d); +#endif +int q_PEM_write_bio_DSA_PUBKEY(BIO *a, DSA *b); +int q_PEM_write_bio_RSA_PUBKEY(BIO *a, RSA *b); +int q_PEM_write_bio_PUBKEY(BIO *a, EVP_PKEY *b); +#ifndef OPENSSL_NO_EC +int q_PEM_write_bio_EC_PUBKEY(BIO *a, EC_KEY *b); +#endif +void q_RAND_seed(const void *a, int b); +int q_RAND_status(); +int q_RAND_bytes(unsigned char *b, int n); +RSA *q_RSA_new(); +void q_RSA_free(RSA *a); +int q_RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); +#if OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) +typedef unsigned int (*q_psk_client_callback_t)(SSL *ssl, const char *hint, char *identity, unsigned int max_identity_len, unsigned char *psk, unsigned int max_psk_len); +void q_SSL_set_psk_client_callback(SSL *ssl, q_psk_client_callback_t callback); +typedef unsigned int (*q_psk_server_callback_t)(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len); +#endif // OPENSSL_VERSION_NUMBER >= 0x10001000L && !defined(OPENSSL_NO_PSK) +int q_SSL_write(SSL *a, const void *b, int c); +int q_X509_cmp(X509 *a, X509 *b); +#ifdef SSLEAY_MACROS +void *q_ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, char *x); +#define q_X509_dup(x509) (X509 *)q_ASN1_dup((i2d_of_void *)q_i2d_X509, \ + (d2i_of_void *)q_d2i_X509,(char *)x509) +#else +X509 *q_X509_dup(X509 *a); +#endif +void q_X509_print(BIO *a, X509*b); +int q_X509_digest(const X509 *x509, const EVP_MD *type, unsigned char *md, unsigned int *len); +ASN1_OBJECT *q_X509_EXTENSION_get_object(X509_EXTENSION *a); +Q_AUTOTEST_EXPORT void q_X509_free(X509 *a); +Q_AUTOTEST_EXPORT ASN1_TIME *q_X509_gmtime_adj(ASN1_TIME *s, long adj); +Q_AUTOTEST_EXPORT void q_ASN1_TIME_free(ASN1_TIME *t); +X509_EXTENSION *q_X509_get_ext(X509 *a, int b); +int q_X509_get_ext_count(X509 *a); +void *q_X509_get_ext_d2i(X509 *a, int b, int *c, int *d); +const X509V3_EXT_METHOD *q_X509V3_EXT_get(X509_EXTENSION *a); +void *q_X509V3_EXT_d2i(X509_EXTENSION *a); +int q_X509_EXTENSION_get_critical(X509_EXTENSION *a); +ASN1_OCTET_STRING *q_X509_EXTENSION_get_data(X509_EXTENSION *a); +void q_BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *a); +void q_AUTHORITY_KEYID_free(AUTHORITY_KEYID *a); +int q_ASN1_STRING_print(BIO *a, const ASN1_STRING *b); +int q_X509_check_issued(X509 *a, X509 *b); +X509_NAME *q_X509_get_issuer_name(X509 *a); +X509_NAME *q_X509_get_subject_name(X509 *a); +ASN1_INTEGER *q_X509_get_serialNumber(X509 *a); +int q_X509_verify_cert(X509_STORE_CTX *ctx); +int q_X509_NAME_entry_count(X509_NAME *a); +X509_NAME_ENTRY *q_X509_NAME_get_entry(X509_NAME *a,int b); +ASN1_STRING *q_X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *a); +ASN1_OBJECT *q_X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *a); +EVP_PKEY *q_X509_PUBKEY_get(X509_PUBKEY *a); +void q_X509_STORE_free(X509_STORE *store); +X509_STORE *q_X509_STORE_new(); +int q_X509_STORE_add_cert(X509_STORE *ctx, X509 *x); +void q_X509_STORE_CTX_free(X509_STORE_CTX *storeCtx); +int q_X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, + X509 *x509, STACK_OF(X509) *chain); +X509_STORE_CTX *q_X509_STORE_CTX_new(); +int q_X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); +int q_X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); +int q_X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); +X509 *q_X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx); +X509 *q_X509_REQ_to_X509(X509_REQ *r, int days, EVP_PKEY *pkey); +int q_PEM_write_bio_X509(BIO *bp, X509 *x); +X509 *q_X509_new(); +int q_ASN1_INTEGER_set(ASN1_INTEGER *a, long v); +int q_X509_set_pubkey(X509 *x, EVP_PKEY *key); +int q_X509_set_issuer_name(X509 *x, X509_NAME *name); +int q_X509_sign(X509 *x, EVP_PKEY *key, const EVP_MD *md); +int q_X509_add_ext(X509 *x, X509_EXTENSION *ex, int location); +int q_X509_set_version(X509 *x, long version); +int q_X509_set_subject_name(X509 *x, X509_NAME *name); +ASN1_OCTET_STRING *q_ASN1_OCTET_STRING_new(); +int q_X509_pubkey_digest(const X509 *data, const EVP_MD *type, unsigned char *md, unsigned int *len); +int q_ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data, int len); +int q_X509_add1_ext_i2d(X509 *x, int nid, void *value, int crit, unsigned long flags); +void q_ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *a); +int q_X509_NAME_digest(const X509_NAME *data, const EVP_MD *type, unsigned char *md, unsigned int *len); +ASN1_INTEGER *q_ASN1_INTEGER_new(); +GENERAL_NAMES *q_GENERAL_NAMES_new(); +GENERAL_NAME *q_GENERAL_NAME_new(); +X509_NAME *q_X509_NAME_dup(X509_NAME *xn); +int q_X509_set_serialNumber(X509 *x, ASN1_INTEGER *serial); +AUTHORITY_KEYID *q_AUTHORITY_KEYID_new(); +void q_ASN1_INTEGER_free(ASN1_INTEGER *a); +int q_i2d_X509_REQ_bio(BIO *bp, X509_REQ *req); +int q_i2d_X509_bio(BIO *bp, X509 *x509); + +// Diffie-Hellman support +DH *q_DH_new(); +void q_DH_free(DH *dh); +DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length); +int q_i2d_DHparams(DH *a, unsigned char **p); +int q_DH_check(DH *dh, int *codes); + +BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); + +#ifndef OPENSSL_NO_EC +// EC Diffie-Hellman support +EC_KEY *q_EC_KEY_dup(const EC_KEY *src); +EC_KEY *q_EC_KEY_new_by_curve_name(int nid); +void q_EC_KEY_free(EC_KEY *ecdh); + +// EC curves management +size_t q_EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +int q_EC_curve_nist2nid(const char *name); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +#endif // OPENSSL_NO_EC +#if OPENSSL_VERSION_NUMBER >= 0x10002000L +#define q_SSL_get_server_tmp_key(ssl, key) q_SSL_ctrl((ssl), SSL_CTRL_GET_SERVER_TMP_KEY, 0, (char *)key) +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + +// PKCS#12 support +int q_PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca); +PKCS12 *q_d2i_PKCS12_bio(BIO *bio, PKCS12 **pkcs12); +void q_PKCS12_free(PKCS12 *pkcs12); + +#define q_BIO_get_mem_data(b, pp) (int)q_BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) +#define q_BIO_pending(b) (int)q_BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) +#define q_SSL_CTX_set_mode(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_MODE,(op),NULL) +#define q_sk_GENERAL_NAME_num(st) q_SKM_sk_num(GENERAL_NAME, (st)) +#define q_sk_GENERAL_NAME_value(st, i) q_SKM_sk_value(GENERAL_NAME, (st), (i)) +#if QT_CONFIG(opensslv11) +#define q_sk_GENERAL_NAME_push(st, val) q_OPENSSL_sk_push((st), (val)) +#else +#define q_sk_GENERAL_NAME_push(st, val) q_SKM_sk_push(GENERAL_NAME, (st), (val)) +#endif + +void q_GENERAL_NAME_free(GENERAL_NAME *a); + +#define q_sk_X509_num(st) q_SKM_sk_num(X509, (st)) +#define q_sk_X509_value(st, i) q_SKM_sk_value(X509, (st), (i)) +#define q_sk_SSL_CIPHER_num(st) q_SKM_sk_num(SSL_CIPHER, (st)) +#define q_sk_SSL_CIPHER_value(st, i) q_SKM_sk_value(SSL_CIPHER, (st), (i)) +#define q_SSL_CTX_add_extra_chain_cert(ctx,x509) \ + q_SSL_CTX_ctrl(ctx,SSL_CTRL_EXTRA_CHAIN_CERT,0,(char *)x509) +#define q_EVP_PKEY_assign_RSA(pkey,rsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ + (char *)(rsa)) +#define q_EVP_PKEY_assign_DSA(pkey,dsa) q_EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ + (char *)(dsa)) +#define q_OpenSSL_add_all_algorithms() q_OPENSSL_add_all_algorithms_conf() +int q_i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp); + +void q_BIO_set_flags(BIO *b, int flags); +void q_BIO_clear_flags(BIO *b, int flags); +void *q_BIO_get_ex_data(BIO *b, int idx); +int q_BIO_set_ex_data(BIO *b, int idx, void *data); + +#define q_BIO_set_retry_read(b) q_BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_retry_write(b) q_BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_clear_retry_flags(b) q_BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) +#define q_BIO_set_app_data(s,arg) q_BIO_set_ex_data(s,0,arg) +#define q_BIO_get_app_data(s) q_BIO_get_ex_data(s,0) + +// Helper function +class QDateTime; +QDateTime q_getTimeFromASN1(const ASN1_TIME *aTime); + +void *q_CRYPTO_malloc(size_t num, const char *file, int line); +#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0) + +QT_END_NAMESPACE + +#endif diff --git a/src/opcua/x509/qopcuakeypair.cpp b/src/opcua/x509/qopcuakeypair.cpp new file mode 100644 index 0000000..90e60fe --- /dev/null +++ b/src/opcua/x509/qopcuakeypair.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuakeypair.h" +#include "qopcuakeypair_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaKeyPair + \inmodule QtOpcUa + \since 5.14 + + \brief QOpcUaKeyPair handles private and public key pairs + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + It can generate, load and store keys for asymmetric encryption. + Instances of this class have to be passed to functions which need a key. +*/ + +/*! + \enum QOpcUaKeyPair::RsaKeyStrength + + This enum type specifies the strength of a RSA key. + + \value Bits1024 + A key strength of 1024 bits. + \value Bits2048 + A key strength of 2048 bits. + \value Bits4096 + A key strength of 4096 bits. +*/ + +/*! + \enum QOpcUaKeyPair::KeyType + + This enum type specifies the type of a key. + + \value Rsa + An RSA key + \value Empty + No key is available. + \value Unknown + The type of key is not handled. +*/ + +/*! + \enum QOpcUaKeyPair::Cipher + + Ciphers for encryption of private keys. + + \value Aes128Cbc + Encrypting AES128 with CBC + \value Unencrypted + The Key will not be encrypted. +*/ + +/*! + \fn QOpcUaKeyPair::passphaseNeeded() + + This signal is emitted when a private key needs a passphrase for en- or decryption. + + Place the passphrase in \a passphrase. + \a writeOperation is \c true when the passphrase is needed for exporting a key, and + is \c false when the passphrase is needed for importing a key. + + \a maximumPassphraseSize specifies the maximum length in bytes for the passphrase. + All characters in \a passphrase exceeding this limit will be ignored. + + In case you have use this signal crossing thread boundaries you have to connect it + with \c Qt::BlockingQueuedConnection. +*/ + +/*! + Creates a new empty key pair. +*/ +QOpcUaKeyPair::QOpcUaKeyPair(QObject *parent) + : QObject(*(new QOpcUaKeyPairPrivate()), parent) +{ +} +/*! + Destroys the key pair. +*/ +QOpcUaKeyPair::~QOpcUaKeyPair() +{ +} + +/*! + Loads a key from PEM encoded data in \a data. + Returns \c true on success and \c false otherwise. + + It detects from the PEM header if the data contains a private or + public key. Loading encrypted keys is possible by connecting a + function to the signal \c passphraseNeeded for provision of the + passphrase. +*/ +bool QOpcUaKeyPair::loadFromPemData(const QByteArray &data) +{ + Q_D(QOpcUaKeyPair); + return d->loadFromPemData(data); +} + +/*! + Loads a key from PEM encoded data in \a data. + Returns \c true on success and \c false otherwise. + + It detects from the PEM header if the data contains a private or + public key. Loading encrypted keys is possible by connecting a + function to the signal \c passphraseNeeded for provision of the + passphrase. +*/ +QByteArray QOpcUaKeyPair::publicKeyToByteArray() const +{ + Q_D(const QOpcUaKeyPair); + return d->publicKeyToByteArray(); +} + +/*! + Returns the type of the current key. +*/ +QOpcUaKeyPair::KeyType QOpcUaKeyPair::type() const +{ + Q_D(const QOpcUaKeyPair); + return d->keyType(); +} + +/*! + Returns \c true if the current key contains a private key, otherwise \c false. +*/ +bool QOpcUaKeyPair::hasPrivateKey() const +{ + Q_D(const QOpcUaKeyPair); + return d->hasPrivateKey(); +} + +/*! + Returns the PEM encoded private key. + In case there is no private key, an empty byte array is returned. + + The encryption of the key has to be specified using the parameters + \a cipher and \a password. In order to store the key unencrypted + the cipher \c Cipher::Unencrypted has to be used. +*/ +QByteArray QOpcUaKeyPair::privateKeyToByteArray(Cipher cipher, const QString &password) const +{ + Q_D(const QOpcUaKeyPair); + return d->privateKeyToByteArray(cipher, password); +} + + +/*! + Generates a new asymmetric RSA key pair. + + The length of the key can be specified by the \c strength parameter. +*/ +void QOpcUaKeyPair::generateRsaKey(QOpcUaKeyPair::RsaKeyStrength strength) +{ + Q_D(QOpcUaKeyPair); + d->generateRsaKey(strength); +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuakeypair.h b/src/opcua/x509/qopcuakeypair.h new file mode 100644 index 0000000..dbd1197 --- /dev/null +++ b/src/opcua/x509/qopcuakeypair.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAKEYPAIR_H +#define QOPCUAKEYPAIR_H + +#include <QObject> +#include <QString> +#include <QSharedPointer> +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaKeyPairPrivate; + +class Q_OPCUA_EXPORT QOpcUaKeyPair : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QOpcUaKeyPair) + +public: + enum class RsaKeyStrength { + Bits1024 = 1024, + Bits2048 = 2048, + Bits4096 = 4096 + }; + + enum class KeyType { + Rsa, + Empty, + Unknown + }; + + enum class Cipher { + Aes128Cbc, + Unencrypted + }; + + QOpcUaKeyPair(QObject *parent = nullptr); + + virtual ~QOpcUaKeyPair(); + bool loadFromPemData(const QByteArray &data); + QByteArray publicKeyToByteArray() const; + QByteArray privateKeyToByteArray(Cipher cipher, const QString &password) const; + KeyType type() const; + bool hasPrivateKey() const; + void generateRsaKey(QOpcUaKeyPair::RsaKeyStrength strength); + +Q_SIGNALS: + void passphraseNeeded(QString &passphrase, int maximumLength, bool writeOperation); + + friend class QOpcUaX509CertificateSigningRequestPrivate; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAKEYPAIR_H diff --git a/src/opcua/x509/qopcuakeypair_openssl.cpp b/src/opcua/x509/qopcuakeypair_openssl.cpp new file mode 100644 index 0000000..66a7b7e --- /dev/null +++ b/src/opcua/x509/qopcuakeypair_openssl.cpp @@ -0,0 +1,264 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuakeypair_p.h" +#include "openssl_symbols_p.h" +#include "qopcuax509utils_p.h" + +QT_BEGIN_NAMESPACE + +QOpcUaKeyPairPrivate::QOpcUaKeyPairPrivate() + : QObjectPrivate() +{ + if (!q_resolveOpenSslSymbols()) + qFatal("Failed to resolve symbols"); + + q_ERR_load_crypto_strings(); +#if OPENSSL_VERSION_NUMBER < 0x10100000L + q_OPENSSL_add_all_algorithms_noconf(); +#endif +} + +QOpcUaKeyPairPrivate::~QOpcUaKeyPairPrivate() +{ + if (m_keyData) { + q_EVP_PKEY_free(m_keyData); + m_keyData = nullptr; + } +} + +static int passwordCallback(char *passphraseBuffer, int maximumPassphraseSize, int writeOperation, void *userData) { + QOpcUaKeyPair *source = reinterpret_cast<QOpcUaKeyPair*>(userData); + QString passphrase; + source->passphraseNeeded(passphrase, maximumPassphraseSize, writeOperation == 1); + + if (passphrase.isEmpty()) + return -1; + + memcpy(passphraseBuffer, passphrase.toUtf8().constData(), qMin(maximumPassphraseSize, passphrase.size())); + return passphrase.size(); +} + +bool QOpcUaKeyPairPrivate::loadFromPemData(const QByteArray &data) { + Q_Q(QOpcUaKeyPair); + + if (m_keyData) { + q_EVP_PKEY_free(m_keyData); + m_keyData = nullptr; + } + m_hasPrivateKey = false; + + BIO *bio = q_BIO_new_mem_buf((void *)data.constData(), data.size()); + if (!bio) { + qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); + return false; + } + Deleter<BIO> bioDeleter(bio, q_BIO_free_all); + + if (data.startsWith("-----BEGIN PRIVATE KEY-----") || data.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----")) { + if (!q_PEM_read_bio_PrivateKey(bio, &m_keyData, &passwordCallback, q /* userData */)) { + qCWarning(lcSsl) << "Failed to load private key:" << getOpenSslError(); + return false; + } + m_hasPrivateKey = true; + } else { + if (!q_PEM_read_bio_PUBKEY(bio, &m_keyData, NULL, NULL)) { + qCWarning(lcSsl) << "Failed to load public key:" << getOpenSslError(); + return false; + } + } + + return true; +} + +QByteArray QOpcUaKeyPairPrivate::publicKeyToByteArray() const +{ + if (!m_keyData) { + qCWarning(lcSsl) << "No public key to write"; + return QByteArray(); + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) { + qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); + return QByteArray(); + } + Deleter<BIO> bioDeleter(bio, q_BIO_free_all); + + if (0 == q_PEM_write_bio_PUBKEY(bio, m_keyData)) { + qCWarning(lcSsl) << "Failed to write public key:" << getOpenSslError(); + return QByteArray(); + } + + char *buf; + int length = q_BIO_get_mem_data(bio, &buf); + QByteArray data(buf, length); + return data; +} + +bool QOpcUaKeyPairPrivate::generateRsaKey(QOpcUaKeyPair::RsaKeyStrength strength) +{ + if (m_keyData) { + q_EVP_PKEY_free(m_keyData); + m_keyData = nullptr; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + EVP_PKEY_CTX *ctx = q_EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr); + if (!ctx) { + qCWarning(lcSsl) << "Failed to allocate context:" << getOpenSslError(); + return false; + } + Deleter<EVP_PKEY_CTX> ctxDeleter(ctx, q_EVP_PKEY_CTX_free); + + if (q_EVP_PKEY_keygen_init(ctx) <= 0) { + qCWarning(lcSsl) << "Failed to initialize context:" << getOpenSslError(); + return false; + } + + if (q_EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, static_cast<int>(strength)) <= 0) { + qCWarning(lcSsl) << "Failed to set context property:" << getOpenSslError(); + return false; + } + + if (q_EVP_PKEY_keygen(ctx, &m_keyData) <= 0) { + qCWarning(lcSsl) << "Failed to generate key:" << getOpenSslError(); + return false; + } + +#else + RSA *rsa; + BIGNUM *publicExponent; + + publicExponent = q_BN_new(); + if (publicExponent == NULL) { + qCWarning(lcSsl) << "Failed to allocate public exponent:" << getOpenSslError(); + return false; + } + Deleter<BIGNUM> publicExponentDeleter(publicExponent, q_BN_free); + + if (q_BN_set_word(publicExponent, RSA_F4) == 0) { + qCWarning(lcSsl) << "Failed to set public exponent:" << getOpenSslError(); + return false; + } + + rsa = q_RSA_new(); + if (rsa == NULL) { + qCWarning(lcSsl) << "Failed to allocate RSA:" << getOpenSslError(); + return false; + } + Deleter<RSA> rsaDeleter(rsa, q_RSA_free); + + int result = q_RSA_generate_key_ex(rsa, static_cast<int>(strength), publicExponent, nullptr /* progress callback */); + if (result == 0) { + qCWarning(lcSsl) << "Failed to generate key:" << getOpenSslError(); + return false; + } + + m_keyData = q_EVP_PKEY_new(); + if (!m_keyData) { + qCWarning(lcSsl) << "Failed to allocate key data:" << getOpenSslError(); + return false; + } + + if (!q_EVP_PKEY_set1_RSA(m_keyData, rsa)) { + qCWarning(lcSsl) << "Failed to transfer key data:" << getOpenSslError(); + return false; + } +#endif + m_hasPrivateKey = true; + return true; +} + +QOpcUaKeyPair::KeyType QOpcUaKeyPairPrivate::keyType() const +{ + if (!m_keyData) + return QOpcUaKeyPair::KeyType::Empty; + switch (q_EVP_PKEY_base_id(m_keyData)) { + case EVP_PKEY_RSA: + return QOpcUaKeyPair::KeyType::Rsa; + default: + return QOpcUaKeyPair::KeyType::Unknown; + } +} + +QByteArray QOpcUaKeyPairPrivate::privateKeyToByteArray(QOpcUaKeyPair::Cipher cipher, const QString &password) const +{ + if (!m_keyData) { + qCWarning(lcSsl) << "No private key to write"; + return QByteArray(); + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) { + qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); + return QByteArray(); + } + Deleter<BIO> bioDeleter(bio, q_BIO_free_all); + + const EVP_CIPHER *enc = NULL; + if (cipher == QOpcUaKeyPair::Cipher::Unencrypted) + enc = NULL; + else if (cipher == QOpcUaKeyPair::Cipher::Aes128Cbc) + enc = q_EVP_aes_128_cbc(); + else { + qCWarning(lcSsl) << "Unknown cipher given"; + return QByteArray(); + } + + if (0 == q_PEM_write_bio_PKCS8PrivateKey(bio, m_keyData, enc, + enc ? (unsigned char*)password.toUtf8().constData() : NULL, + enc ? password.length() : 0, + NULL /* callback */, NULL /* userdata */)) { + qCWarning(lcSsl) << "Failed to write private key:" << getOpenSslError(); + return QByteArray(); + } + + char *buf; + int length = q_BIO_get_mem_data(bio, &buf); + QByteArray data(buf, length); + return data; +} + +bool QOpcUaKeyPairPrivate::hasPrivateKey() const +{ + if (!m_keyData) + return false; + + return m_hasPrivateKey; +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuakeypair_p.h b/src/opcua/x509/qopcuakeypair_p.h new file mode 100644 index 0000000..06b5565 --- /dev/null +++ b/src/opcua/x509/qopcuakeypair_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QOPCUAPUBLICKEYPRIVATE_H +#define QOPCUAPUBLICKEYPRIVATE_H + +#include <private/qobject_p.h> +#include "qopcuakeypair.h" +#include <QtCore/QLoggingCategory> +#include <openssl/rsa.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcSsl) + +class QOpcUaKeyPairPrivate : public QObjectPrivate +{ +public: + QOpcUaKeyPairPrivate(); + ~QOpcUaKeyPairPrivate(); + + bool generateRsaKey(QOpcUaKeyPair::RsaKeyStrength strength); + bool loadFromPemData(const QByteArray &data); + QByteArray publicKeyToByteArray() const; + QByteArray privateKeyToByteArray(QOpcUaKeyPair::Cipher cipher, const QString &password) const; + QOpcUaKeyPair::KeyType keyType() const; + bool hasPrivateKey() const; + +protected: + EVP_PKEY *m_keyData = nullptr; + bool m_hasPrivateKey = false; + +private: + Q_DECLARE_PUBLIC(QOpcUaKeyPair) + + friend class QOpcUaX509CertificateSigningRequestPrivate; +}; +QT_END_NAMESPACE + +#endif // QOPCUAPUBLICKEYPRIVATE_H diff --git a/src/opcua/x509/qopcuax509certificatesigningrequest.cpp b/src/opcua/x509/qopcuax509certificatesigningrequest.cpp new file mode 100644 index 0000000..7bd808f --- /dev/null +++ b/src/opcua/x509/qopcuax509certificatesigningrequest.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509certificatesigningrequest.h" +#include "openssl_symbols_p.h" + +#include "qopcuakeypair_p.h" +#include "qopcuax509certificatesigningrequest_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509CertificateSigningRequest + \inmodule QtOpcUa + \since 5.14 + + \brief QOpcUaX509CertificateSigningRequest create a certificate signing request + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + Before actually creating the singing request data, any extension needed for that + specific request has to be added. Current supported extensions are SubjectAlternativeName, + BasicConstrains, KeyUsage and ExtendedKeyUsage. + + \code + // Generate key + QOpcUaKeyPair key; + key.generateRsaKey(QOpcUaKeyPair::RsaKeyStrength::Bits1024); + + QOpcUaX509CertificateSigningRequest csr; + + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CommonName, "QtOpcUaViewer"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, "DE"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, "The Qt Company"); + csr.setSubject(dn); + + QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; + san->addData(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, "foo.com"); + san->addData(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, "foo.com"); + san->addData(QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, "urn:foo.com:The%20Qt%20Company:QtOpcUaViewer"); + san->setCritical(true); + csr.addExtension(san); + + QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; + bc->setCa(false); + bc->setCritical(true); + csr.addExtension(bc); + + QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; + ku->setCritical(true); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); + csr.addExtension(ku); + + QOpcUaX509ExtensionExtendedKeyUsage *eku = new QOpcUaX509ExtensionExtendedKeyUsage; + eku->setCritical(true); + eku->setKeyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::EmailProtection); + csr.addExtension(eku); + + QByteArray csrData = csr.createRequest(key); + \endcode + + \sa QOpcUaX509ExtensionSubjectAlternativeName, QOpcUaX509ExtensionBasicConstraints, QOpcUaX509ExtensionKeyUsage, QOpcUaX509ExtensionKeyUsage +*/ + +/*! + \enum QOpcUaX509CertificateSigningRequest::MessageDigest + + This enum type specifies the message digest to be used. + + \value SHA256 + Using the SHA256 message digest +*/ + +/*! + \enum QOpcUaX509CertificateSigningRequest::Encoding + + This enum type specifies the encoding of the generated certificate siging request. + + \value PEM + Using PEM encoding + \value DER + Using DER encoding +*/ + +/*! + Creates an empty certificate signing request. +*/ +QOpcUaX509CertificateSigningRequest::QOpcUaX509CertificateSigningRequest() + : d_ptr(new QOpcUaX509CertificateSigningRequestPrivate) +{ + setEncoding(Encoding::PEM); +} + +/*! + Destroys the request and frees all extensions. +*/ +QOpcUaX509CertificateSigningRequest::~QOpcUaX509CertificateSigningRequest() +{ +} + +/*! + Sets the used message digest to \a digest. + The default message digest is SHA256. +*/ +void QOpcUaX509CertificateSigningRequest::setMessageDigest(QOpcUaX509CertificateSigningRequest::MessageDigest digest) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + d->setMessageDigest(digest); +} + +/*! + Returns the used message digest. +*/ +QOpcUaX509CertificateSigningRequest::MessageDigest QOpcUaX509CertificateSigningRequest::messageDigest() const +{ + Q_D(const QOpcUaX509CertificateSigningRequest); + return d->messageDigest(); +} + +/*! + Returns the used request encoding. +*/ +QOpcUaX509CertificateSigningRequest::Encoding QOpcUaX509CertificateSigningRequest::encoding() const +{ + Q_D(const QOpcUaX509CertificateSigningRequest); + return d->encoding(); +} + +/*! + Sets the used request encoding to \a encoding. + The default request encoding is PEM. +*/ +void QOpcUaX509CertificateSigningRequest::setEncoding(QOpcUaX509CertificateSigningRequest::Encoding encoding) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + d->setEncoding(encoding); +} + +/*! + Adds a certificate extension to the request. + + The ownership of the extension object will be transferred to this class. + + \sa QOpcUaX509ExtensionSubjectAlternativeName, QOpcUaX509ExtensionBasicConstraints, QOpcUaX509ExtensionKeyUsage, QOpcUaX509ExtensionKeyUsage +*/ +void QOpcUaX509CertificateSigningRequest::addExtension(QOpcUaX509Extension *extension) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + d->addExtension(extension); +} + +/*! + Sets the subject for this request. + Without a subject it is not possible to generate the request. +*/ +void QOpcUaX509CertificateSigningRequest::setSubject(const QOpcUaX509DistinguishedName &subject) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + d->setSubject(subject); +} + +/*! + Returns the subject of this request. +*/ +const QOpcUaX509DistinguishedName &QOpcUaX509CertificateSigningRequest::subject() const +{ + Q_D(const QOpcUaX509CertificateSigningRequest); + return d->subject(); +} + +/*! + Creates a certificate signing request to be the to a CA for signing. + The private key in \a privateKey is used to sign the request. + The request data is returned as a byte array in the encoding set by setEncoding(). +*/ +QByteArray QOpcUaX509CertificateSigningRequest::createRequest(const QOpcUaKeyPair &privateKey) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + return d->createRequest(privateKey); +} + +/*! + Creates a self-signed certificate from this request for immediate use. + The private key in \a privateKey is used to sign the request. + A validity in days can be specified in \a validityInDays. + The request data is returned as a byte array in the encoding set by setEncoding(). +*/ +QByteArray QOpcUaX509CertificateSigningRequest::createSelfSignedCertificate(const QOpcUaKeyPair &privateKey, int validityInDays) +{ + Q_D(QOpcUaX509CertificateSigningRequest); + return d->createSelfSignedCertificate(privateKey, validityInDays); +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509certificatesigningrequest.h b/src/opcua/x509/qopcuax509certificatesigningrequest.h new file mode 100644 index 0000000..eb750c0 --- /dev/null +++ b/src/opcua/x509/qopcuax509certificatesigningrequest.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509CERTIFICATESIGNINGREQUEST_H +#define QOPCUAX509CERTIFICATESIGNINGREQUEST_H + +#include "QtOpcUa/qopcuakeypair.h" +#include "QtOpcUa/qopcuax509extension.h" +#include "QtOpcUa/qopcuax509distinguishedname.h" +#include <QVector> +#include <QtOpcUa/qopcuaglobal.h> +#include <QtOpcUa/qopcuax509certificatesigningrequest.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaX509CertificateSigningRequestPrivate; + +class Q_OPCUA_EXPORT QOpcUaX509CertificateSigningRequest +{ + Q_DECLARE_PRIVATE(QOpcUaX509CertificateSigningRequest) + +public: + enum class MessageDigest { + SHA256 + }; + enum class Encoding { + PEM, + DER + }; + + QOpcUaX509CertificateSigningRequest(); + ~QOpcUaX509CertificateSigningRequest(); + + void setMessageDigest(MessageDigest digest); + MessageDigest messageDigest() const; + + void setEncoding(Encoding encoding); + Encoding encoding() const; + + void setSubject(const QOpcUaX509DistinguishedName &subject); + const QOpcUaX509DistinguishedName &subject() const; + + void addExtension(QOpcUaX509Extension *extension); + QByteArray createRequest(const QOpcUaKeyPair &privateKey); + QByteArray createSelfSignedCertificate(const QOpcUaKeyPair &privateKey, int validityInDays = 365); + +private: + QOpcUaX509CertificateSigningRequestPrivate *d_ptr = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAX509CERTIFICATESIGNINGREQUEST_H diff --git a/src/opcua/x509/qopcuax509certificatesigningrequest_openssl.cpp b/src/opcua/x509/qopcuax509certificatesigningrequest_openssl.cpp new file mode 100644 index 0000000..4ec4421 --- /dev/null +++ b/src/opcua/x509/qopcuax509certificatesigningrequest_openssl.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509certificatesigningrequest.h" +#include "openssl_symbols_p.h" +#include "qopcuakeypair_p.h" +#include "qopcuax509utils_p.h" +#include "qopcuax509distinguishedname.h" +#include "qopcuax509extensionsubjectalternativename.h" +#include "qopcuax509certificatesigningrequest_p.h" +#include <QOpcUaX509ExtensionBasicConstraints> +#include <QOpcUaX509ExtensionKeyUsage> +#include <QOpcUaX509ExtensionExtendedKeyUsage> + + +QT_BEGIN_NAMESPACE + +QOpcUaX509CertificateSigningRequestPrivate::QOpcUaX509CertificateSigningRequestPrivate() +{ + +} + +QOpcUaX509CertificateSigningRequestPrivate::~QOpcUaX509CertificateSigningRequestPrivate() +{ + qDeleteAll(m_extensions); +} + +void QOpcUaX509CertificateSigningRequestPrivate::setMessageDigest(QOpcUaX509CertificateSigningRequest::MessageDigest digest) +{ + m_messageDigest = digest; +} + +QOpcUaX509CertificateSigningRequest::MessageDigest QOpcUaX509CertificateSigningRequestPrivate::messageDigest() const +{ + return m_messageDigest; +} + +void QOpcUaX509CertificateSigningRequestPrivate::addExtension(QOpcUaX509Extension *extension) +{ + m_extensions.append(extension); +} + +void QOpcUaX509CertificateSigningRequestPrivate::setSubject(const QOpcUaX509DistinguishedName &subject) +{ + m_subject = subject; +} + +QOpcUaX509CertificateSigningRequest::Encoding QOpcUaX509CertificateSigningRequestPrivate::encoding() const +{ + return m_encoding; +} + +void QOpcUaX509CertificateSigningRequestPrivate::setEncoding(QOpcUaX509CertificateSigningRequest::Encoding encoding) +{ + m_encoding = encoding; +} + +const QOpcUaX509DistinguishedName &QOpcUaX509CertificateSigningRequestPrivate::subject() const +{ + return m_subject; +} + +static X509_EXTENSION *createExtension(QOpcUaX509Extension *extension) +{ + X509_EXTENSION *ex = nullptr; + + if (const auto *san = dynamic_cast<const QOpcUaX509ExtensionSubjectAlternativeName *>(extension)) { + QStringList data; + + for (const auto &pair : qAsConst(san->entries())) { + QString prefix; + if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS) + prefix = QLatin1String("DNS:"); + else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::Email) + prefix = QLatin1String("EMAIL:"); + else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::IP) + prefix = QLatin1String("IP:"); + else if (pair.first == QOpcUaX509ExtensionSubjectAlternativeName::Type::URI) + prefix = QLatin1String("URI:"); + else { + qCWarning(lcSsl()) << "Invalid SubjectAlternativeName type"; + return nullptr; + } + + if (pair.second.isEmpty() || pair.second.contains(',')) { + qCWarning(lcSsl()) << "Invalid SubjectAlternativeName value"; + return nullptr; + } + + data.append(prefix + pair.second); + } + + ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, data.join(QLatin1Char(',')).toUtf8().data()); + if (!ex) { + qCWarning(lcSsl()) << "Failed to create X509 extension" << data; + return nullptr; + } + q_X509_EXTENSION_set_critical(ex, san->critical() ? 1 : 0); + } else if (const auto *bc = dynamic_cast<const QOpcUaX509ExtensionBasicConstraints *>(extension)) { + QString data = QLatin1String("CA:") + QLatin1String(bc->ca() ? "true" : "false"); + if (bc->ca() && bc->pathLength() >= 0) + data.append(QLatin1String(",pathlen:") + QString::number(bc->pathLength())); + + ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_basic_constraints, data.toUtf8().data()); + if (!ex) { + qCWarning(lcSsl()) << "Failed to create X509 extension" << data; + return nullptr; + } + q_X509_EXTENSION_set_critical(ex, bc->critical() ? 1 : 0); + } else if (const auto *ku = dynamic_cast<const QOpcUaX509ExtensionKeyUsage *>(extension)) { + QStringList data; + + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature)) + data.append(QLatin1String("Digital Signature")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation)) + data.append(QLatin1String("Non Repudiation")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment)) + data.append(QLatin1String("Key Encipherment")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment)) + data.append(QLatin1String("Data Encipherment")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyAgreement)) + data.append(QLatin1String("Key Agreement")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning)) + data.append(QLatin1String("Certificate Sign")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CrlSigning)) + data.append(QLatin1String("CRL Sign")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::EnciptherOnly)) + data.append(QLatin1String("Encipther Only")); + if (ku->keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DecipherOnly)) + data.append(QLatin1String("Decipher Only")); + + ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_key_usage, data.join(QLatin1Char(',')).toUtf8().data()); + if (!ex) { + qCWarning(lcSsl()) << "Failed to create X509 extension" << data; + return nullptr; + } + q_X509_EXTENSION_set_critical(ex, ku->critical() ? 1 : 0); + } else if (const auto *eku = dynamic_cast<const QOpcUaX509ExtensionExtendedKeyUsage *>(extension)) { + QStringList data; + + if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::TlsWebServerAuthentication)) + data.append(QLatin1String("SSL Server")); + if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::TlsWebClientAuthentication)) + data.append(QLatin1String("SSL Client")); + if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::SignExecutableCode)) + data.append(QLatin1String("Object Signing")); + if (eku->keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::EmailProtection)) + data.append(QLatin1String("S/MIME")); + + // NID_ext_key_usage + ex = q_X509V3_EXT_conf_nid(NULL, NULL, NID_ext_key_usage, data.join(QLatin1Char(',')).toUtf8().data()); + if (!ex) { + qCWarning(lcSsl()) << "Failed to create X509 extension" << data; + return nullptr; + } + q_X509_EXTENSION_set_critical(ex, eku->critical() ? 1 : 0); + } else { + qCWarning(lcSsl) << "Unknown X509 extension"; + return nullptr; + } + return ex; +} + +static bool setSubjectName(X509_NAME *subject, const QOpcUaX509DistinguishedName &dn) { + QVector<QOpcUaX509DistinguishedName::Type> entries = { + QOpcUaX509DistinguishedName::Type::CommonName, + QOpcUaX509DistinguishedName::Type::CountryName, + QOpcUaX509DistinguishedName::Type::LocalityName, + QOpcUaX509DistinguishedName::Type::StateOrProvinceName, + QOpcUaX509DistinguishedName::Type::OrganizationName, + }; + + for (const auto &type : entries) { + const auto value = dn.entry(type); + if (value.isEmpty()) + continue; + + ASN1_OBJECT *obj = q_OBJ_txt2obj(QOpcUaX509DistinguishedName::typeToOid(type).toLatin1().constData(), 1 /* no names allowed */); + if (!obj) { + qCWarning(lcSsl()) << "Invalid distinguishedName type"; + return false; + } + + if (!q_X509_NAME_add_entry_by_OBJ(subject, obj, MBSTRING_UTF8, (const unsigned char*)(value.toUtf8().constData()), -1, -1, 0)) { + qCWarning(lcSsl) << "Failed to set CSR enrty:" << getOpenSslError(); + return false; + } + } + return true; +} + +// Creates the request and returns a PEM encoded byte array +QByteArray QOpcUaX509CertificateSigningRequestPrivate::createRequest(const QOpcUaKeyPair &privateKey) +{ + if (!privateKey.hasPrivateKey()) { + qCWarning(lcSsl) << "Key has no private key"; + return QByteArray(); + } + + auto keyData = privateKey.d_func()->m_keyData; + + X509_REQ *req = q_X509_REQ_new(); + if (!req) { + qCWarning(lcSsl) << "Failed to create CSR:" << getOpenSslError(); + return QByteArray(); + } + Deleter<X509_REQ> reqDeleter(req, q_X509_REQ_free); + + if (!q_X509_REQ_set_version(req, 0 /* version */)) { + qCWarning(lcSsl) << "Failed to set CSR version:" << getOpenSslError(); + return QByteArray(); + } + + X509_NAME *subj = q_X509_REQ_get_subject_name(req); + if (!subj) { + qCWarning(lcSsl) << "Invalid subject pointer"; + return QByteArray(); + } + + if (!setSubjectName(subj, m_subject)) { + qCWarning(lcSsl) << "Failed to set subject"; + return QByteArray(); + } + + if (m_extensions.size() > 0) { + auto exts = q_sk_X509_EXTENSION_new_null(); + + for (auto extension : m_extensions) { + auto ex = createExtension(extension); + if (ex) + q_sk_X509_EXTENSION_push(exts, ex); // returns void + } + if (q_X509_REQ_add_extensions(req, (STACK_OF(X509_EXTENSION) *)exts) == 0) { + qCWarning(lcSsl) << "Failed to add X509 extensions"; + return QByteArray(); + } + q_sk_X509_EXTENSION_pop_free(exts, (void(*)(void*))q_X509_EXTENSION_free); // frees the whole stack, returns void + } // end of for loop + + if (!q_X509_REQ_set_pubkey(req, keyData)) { + qCWarning(lcSsl) << "Failed to set public key:" << getOpenSslError(); + return QByteArray(); + } + + const EVP_MD *digest = nullptr; + if (m_messageDigest == QOpcUaX509CertificateSigningRequest::MessageDigest::SHA256) + digest = q_EVP_sha256(); + + if (!digest) { + qCWarning(lcSsl) << "Invalid message digest"; + return QByteArray(); + } + + if (q_X509_REQ_sign(req, keyData, digest) <= 0) { + qCWarning(lcSsl) << "Failed to sign CSR:" << getOpenSslError(); + return QByteArray(); + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) { + qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); + return QByteArray(); + } + Deleter<BIO> bioDeleter(bio, q_BIO_free_all); + + int result = 0; + + if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::PEM) { + // Some CAs require to use q_PEM_write_bio_X509_REW_NEW + result = q_PEM_write_bio_X509_REQ(bio, req); + } else if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::DER) { + result = q_i2d_X509_REQ_bio(bio, req); + } + if (result != 1) { + qCWarning(lcSsl) << "Failed to export certificate request"; + return QByteArray(); + } + + char *buf; + int length = q_BIO_get_mem_data(bio, &buf); + QByteArray data(buf, length); + return data; +} + +QByteArray QOpcUaX509CertificateSigningRequestPrivate::createSelfSignedCertificate(const QOpcUaKeyPair &privateKey, int validityInDays) +{ + if (!privateKey.hasPrivateKey()) { + qCWarning(lcSsl) << "Key has no private key"; + return QByteArray(); + } + + auto keyData = privateKey.d_func()->m_keyData; + + X509 *x509 = q_X509_new(); + if (!x509) + return QByteArray(); + + Deleter<X509> x509Deleter(x509, q_X509_free); + + if (!q_X509_set_version(x509, 2 /* version */)) { + qCWarning(lcSsl) << "Failed to set version"; + return QByteArray(); + } + q_X509_gmtime_adj(q_X509_getm_notBefore(x509), 0); // current time + q_X509_gmtime_adj(q_X509_getm_notAfter(x509), (long)60 * 60 * 24 * validityInDays); + + if (!q_X509_set_pubkey(x509, keyData)) { + qCWarning(lcSsl) << "Failed to set public key:" << getOpenSslError(); + return QByteArray(); + } + + X509_NAME *subj = q_X509_get_subject_name(x509); + if (!subj) { + qCWarning(lcSsl) << "Invalid subject pointer"; + return QByteArray(); + } + + if (!setSubjectName(subj, m_subject)) { + qCWarning(lcSsl) << "Failed to set subject"; + return QByteArray(); + } + + X509_NAME *issuer = q_X509_get_issuer_name(x509); + if (!issuer) { + qCWarning(lcSsl) << "Invalid issuer pointer"; + return QByteArray(); + } + + if (!setSubjectName(issuer, m_subject)) { + qCWarning(lcSsl) << "Failed to set issuer"; + return QByteArray(); + } + + for (auto extension : m_extensions) { + auto ex = createExtension(extension); + if (ex) { + if (!q_X509_add_ext(x509, ex, -1)) { + qCWarning(lcSsl) << "Failed to add extension"; + return QByteArray(); + } + q_X509_EXTENSION_free(ex); + } else { + qCWarning(lcSsl) << "Invalid extension"; + return QByteArray(); + } + } + + // Hash of public key + unsigned char publicKeyHash[SHA_DIGEST_LENGTH]; + unsigned int len; + if (!q_X509_pubkey_digest(x509, q_EVP_sha1(), publicKeyHash, &len)) { + qCWarning(lcSsl) << "Failed to hash public key"; + return QByteArray(); + } + + // Set subject key identifier + ASN1_OCTET_STRING *subjectKeyIdentifier = q_ASN1_OCTET_STRING_new(); + if (!subjectKeyIdentifier) { + qCWarning(lcSsl) << "Failed to allocate ASN1 string"; + return QByteArray(); + } + Deleter<ASN1_OCTET_STRING> subjectKeyIdentifierDeleter(subjectKeyIdentifier, q_ASN1_OCTET_STRING_free); + + if (!q_ASN1_OCTET_STRING_set(subjectKeyIdentifier, publicKeyHash, SHA_DIGEST_LENGTH)) { + qCWarning(lcSsl) << "Failed set ASN1 string"; + return QByteArray(); + } + + if (!q_X509_add1_ext_i2d(x509, NID_subject_key_identifier, subjectKeyIdentifier, 0, X509V3_ADD_DEFAULT)) { + qCWarning(lcSsl) << "Failed to add subject key identifier extension"; + return QByteArray(); + } + + // Set serial number + unsigned char subjHash[SHA_DIGEST_LENGTH]; + unsigned char finalHash[SHA_DIGEST_LENGTH]; + + if (!q_X509_NAME_digest(subj, q_EVP_sha1(), subjHash, &len)) { + qCWarning(lcSsl) << "failed"; + return QByteArray(); + } + for (unsigned int i = 0; i < len; i++) + finalHash[i] = subjHash[i] ^ publicKeyHash[i]; + + ASN1_INTEGER *serial_num = q_ASN1_INTEGER_new(); + if (!serial_num) { + qCWarning(lcSsl) << "Failed to allocate ASN1 integer"; + return QByteArray(); + } + Deleter<ASN1_OCTET_STRING> serial_numDeleter(serial_num, q_ASN1_INTEGER_free); + + if (!q_ASN1_OCTET_STRING_set(serial_num, finalHash, len)) { + qCWarning(lcSsl) << "Failed to set ASN1 integer"; + return QByteArray(); + } + if (!q_X509_set_serialNumber(x509, serial_num)) { + qCWarning(lcSsl) << "Failed to set serial number"; + return QByteArray(); + } + + // Set authority key identifier + AUTHORITY_KEYID *akid = q_AUTHORITY_KEYID_new(); + if (!akid) { + qCWarning(lcSsl) << "Failed to allocate authority key id"; + return QByteArray(); + } + Deleter<AUTHORITY_KEYID> akidDeleter(akid, q_AUTHORITY_KEYID_free); + + akid->issuer = q_GENERAL_NAMES_new(); + if (!akid->issuer) { + qCWarning(lcSsl) << "Failed to set authority key id"; + return QByteArray(); + } + + GENERAL_NAME *generalName = q_GENERAL_NAME_new(); + if (!generalName) { + qCWarning(lcSsl) << "Failed to set authority key id"; + return QByteArray(); + } + generalName->type = GEN_DIRNAME; + generalName->d.directoryName = q_X509_NAME_dup(q_X509_get_subject_name(x509)); + +#if QT_CONFIG(opensslv11) + q_sk_GENERAL_NAME_push((OPENSSL_STACK*)akid->issuer, generalName); +#else + q_sk_GENERAL_NAME_push(akid->issuer, generalName); +#endif + akid->keyid = (ASN1_OCTET_STRING*)q_X509_get_ext_d2i(x509, NID_subject_key_identifier, NULL, NULL); + akid->serial = q_ASN1_INTEGER_dup(q_X509_get_serialNumber(x509)); + + if (!q_X509_add1_ext_i2d(x509, NID_authority_key_identifier, akid, 0, X509V3_ADD_DEFAULT)) { + qCWarning(lcSsl) << "Failed to add authority key id extension"; + return QByteArray(); + } + + const EVP_MD *digest = nullptr; + if (m_messageDigest == QOpcUaX509CertificateSigningRequest::MessageDigest::SHA256) + digest = q_EVP_sha256(); + + if (!digest) { + qCWarning(lcSsl) << "Invalid message digest"; + return QByteArray(); + } + + if (q_X509_sign(x509, keyData, digest) <= 0) { + qCWarning(lcSsl) << "Failed to sign certificate:" << getOpenSslError(); + return QByteArray(); + } + + BIO *bio = q_BIO_new(q_BIO_s_mem()); + if (!bio) { + qCWarning(lcSsl) << "Failed to allocate a buffer:" << getOpenSslError(); + return QByteArray(); + } + Deleter<BIO> bioDeleter(bio, q_BIO_free_all); + + int result = 0; + + if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::PEM) { + result = q_PEM_write_bio_X509(bio, x509); + } else if (m_encoding == QOpcUaX509CertificateSigningRequest::Encoding::DER) { + result = q_i2d_X509_bio(bio, x509); + } + if (result != 1) { + qCWarning(lcSsl) << "Failed to export certificate"; + return QByteArray(); + } + + char *buf; + int length = q_BIO_get_mem_data(bio, &buf); + QByteArray data(buf, length); + return data; +} + +QT_END_NAMESPACE + diff --git a/src/opcua/x509/qopcuax509certificatesigningrequest_p.h b/src/opcua/x509/qopcuax509certificatesigningrequest_p.h new file mode 100644 index 0000000..6406d46 --- /dev/null +++ b/src/opcua/x509/qopcuax509certificatesigningrequest_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QOPCUAX509CERTIFICATESIGNINGREQUESTPRIVATE_H +#define QOPCUAX509CERTIFICATESIGNINGREQUESTPRIVATE_H + +#include "QtOpcUa/qopcuax509extension.h" +#include "QtOpcUa/qopcuax509distinguishedname.h" +#include "QtOpcUa/qopcuakeypair.h" +#include <QVector> +#include <QtOpcUa/qopcuaglobal.h> +#include "qopcuax509certificatesigningrequest.h" + +QT_BEGIN_NAMESPACE + +class QOpcUaX509CertificateSigningRequestPrivate +{ +public: + QOpcUaX509CertificateSigningRequestPrivate(); + ~QOpcUaX509CertificateSigningRequestPrivate(); + + void setMessageDigest(QOpcUaX509CertificateSigningRequest::MessageDigest); + QOpcUaX509CertificateSigningRequest::MessageDigest messageDigest() const; + + QOpcUaX509CertificateSigningRequest::Encoding encoding() const; + void setEncoding(QOpcUaX509CertificateSigningRequest::Encoding encoding); + + const QOpcUaX509DistinguishedName& subject() const; + void setSubject(const QOpcUaX509DistinguishedName &subject); + + void addExtension(QOpcUaX509Extension *extension); + QByteArray createRequest(const QOpcUaKeyPair &privateKey); + QByteArray createSelfSignedCertificate(const QOpcUaKeyPair &privateKey, int validityInDays); + +private: + QVector<QOpcUaX509Extension *> m_extensions; + QOpcUaX509CertificateSigningRequest::MessageDigest m_messageDigest = QOpcUaX509CertificateSigningRequest::MessageDigest::SHA256; + QOpcUaX509DistinguishedName m_subject; + QOpcUaX509CertificateSigningRequest::Encoding m_encoding = QOpcUaX509CertificateSigningRequest::Encoding::PEM; + + Q_DECLARE_PUBLIC(QOpcUaX509CertificateSigningRequest) + QOpcUaX509CertificateSigningRequest *q_ptr = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAX509CERTIFICATESIGNINGREQUESTPRIVATE_H diff --git a/src/opcua/x509/qopcuax509distinguishedname.cpp b/src/opcua/x509/qopcuax509distinguishedname.cpp new file mode 100644 index 0000000..a4c39b0 --- /dev/null +++ b/src/opcua/x509/qopcuax509distinguishedname.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509distinguishedname.h" +#include <QtCore/QMap> + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509DistinguishedName + \inmodule QtOpcUa + \since 5.14 + + \brief Information about a distinguished name item + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + \code + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CommonName, "QtOpcUaViewer"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, "DE"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, "The Qt Company"); + \endcode + + \sa QOpcUaX509CertificateSigningRequest +*/ + +/*! + \enum QOpcUaX509DistinguishedName::Type + + Enum with entry types for X509DistinguishedName. + + \value CommonName + Common name + \value CountryName + Country name + \value LocalityName + Locality name + \value StateOrProvinceName + State or provice name + \value OrganizationName + Organization name +*/ + +class QOpcUaX509DistinguishedNameData : public QSharedData +{ +public: + QMap<QOpcUaX509DistinguishedName::Type, QString> entries; +}; + +/*! + Constructs an empty X509DistinguishedName. +*/ +QOpcUaX509DistinguishedName::QOpcUaX509DistinguishedName() + : data(new QOpcUaX509DistinguishedNameData) +{ +} + +/*! + Destructs a X509DistinguishedName. +*/ +QOpcUaX509DistinguishedName::~QOpcUaX509DistinguishedName() +{ +} + +/*! + Constructs a X509DistinguishedName from \a rhs. +*/ +QOpcUaX509DistinguishedName::QOpcUaX509DistinguishedName(const QOpcUaX509DistinguishedName &rhs) + : data(rhs.data) +{ +} + +/*! + Sets the values from \a rhs in this X509DistinguishedName. +*/ +QOpcUaX509DistinguishedName &QOpcUaX509DistinguishedName::operator=(const QOpcUaX509DistinguishedName &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Returns \c true if this X509DistinguishedName has the same value as \a rhs. +*/ +bool QOpcUaX509DistinguishedName::operator==(const QOpcUaX509DistinguishedName &rhs) const +{ + return data->entries == rhs.data->entries; +} + +/*! + Sets the entry of \a type to \a value. + Already existing types will be overwritten. +*/ +void QOpcUaX509DistinguishedName::setEntry(QOpcUaX509DistinguishedName::Type type, const QString &value) +{ + data->entries.insert(type, value); +} + +/*! + Returns the object id string for \a type. +*/ +QString QOpcUaX509DistinguishedName::typeToOid(QOpcUaX509DistinguishedName::Type type) +{ + switch (type) { + case Type::CommonName: + return QLatin1String("2.5.4.3"); + case Type::CountryName: + return QLatin1String("2.5.4.6"); + case Type::LocalityName: + return QLatin1String("2.5.4.7"); + case Type::StateOrProvinceName: + return QLatin1String("2.5.4.8"); + case Type::OrganizationName: + return QLatin1String("2.5.4.10"); + default: + return QString(); + } +} + +/*! + Returns value for a \a type. +*/ +QString QOpcUaX509DistinguishedName::entry(QOpcUaX509DistinguishedName::Type type) const +{ + return data->entries.value(type); +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509distinguishedname.h b/src/opcua/x509/qopcuax509distinguishedname.h new file mode 100644 index 0000000..f7df9f2 --- /dev/null +++ b/src/opcua/x509/qopcuax509distinguishedname.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUADISTINGUISHEDNAME_H +#define QOPCUADISTINGUISHEDNAME_H + +#include <QtOpcUa/qopcuaglobal.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaX509DistinguishedNameData; +class Q_OPCUA_EXPORT QOpcUaX509DistinguishedName +{ +public: + enum class Type { + CommonName, // 2.5.4.3 + CountryName, // 2.5.4.6 + LocalityName, // 2.5.4.7 + StateOrProvinceName, // 2.5.4.8 + OrganizationName, // 2.5.4.10 + }; + + QOpcUaX509DistinguishedName(); + QOpcUaX509DistinguishedName(const QOpcUaX509DistinguishedName &); + QOpcUaX509DistinguishedName &operator=(const QOpcUaX509DistinguishedName &); + bool operator==(const QOpcUaX509DistinguishedName &rhs) const; + ~QOpcUaX509DistinguishedName(); + void setEntry(Type type, const QString &value); + QString entry(Type type) const; + static QString typeToOid(Type type); + +private: + QSharedDataPointer<QOpcUaX509DistinguishedNameData> data; +}; + +QT_END_NAMESPACE + +#endif // QOPCUADISTINGUISHEDNAME_H diff --git a/src/opcua/x509/qopcuax509extension.cpp b/src/opcua/x509/qopcuax509extension.cpp new file mode 100644 index 0000000..566a8de --- /dev/null +++ b/src/opcua/x509/qopcuax509extension.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509extension.h" +#include "qopcuax509extension_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509Extension + \inmodule QtOpcUa + \since 5.14 + + \brief Base class for all X509 extensions + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. + + \sa QOpcUaX509ExtensionSubjectAlternativeName, QOpcUaX509ExtensionBasicConstraints, QOpcUaX509ExtensionKeyUsage, QOpcUaX509ExtensionKeyUsage +*/ + +/*! + Constructs a default X509Extension. +*/ +QOpcUaX509Extension::QOpcUaX509Extension() + : data(new QOpcUaX509ExtensionData) +{ +} + +/*! + Constructs a X509Extension from \a rhs. +*/ +QOpcUaX509Extension::QOpcUaX509Extension(const QOpcUaX509Extension &rhs) + : data(rhs.data) +{ +} + +/*! + Constructs a X509Extension from \a rhs. +*/ +QOpcUaX509Extension::QOpcUaX509Extension(QSharedDataPointer<QOpcUaX509ExtensionData> rhs) + : data(rhs) +{ +} + +/*! + Returns \c true if this X509Extension has the same value as \a rhs. +*/ +bool QOpcUaX509Extension::operator==(const QOpcUaX509Extension &rhs) const +{ + return data->critical == rhs.data->critical; +} + +/*! + Sets the values from \a rhs in this X509Extension. +*/ +QOpcUaX509Extension &QOpcUaX509Extension::operator=(const QOpcUaX509Extension &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Destructs a X509Extension. +*/ +QOpcUaX509Extension::~QOpcUaX509Extension() +{ +} + +/*! + Sets the critical flag to \a critical. +*/ +void QOpcUaX509Extension::setCritical(bool critical) +{ + data->critical = critical; +} + +/*! + Return the state of the critical flag. +*/ +bool QOpcUaX509Extension::critical() const +{ + return data->critical; +} + +QOpcUaX509Extension::QOpcUaX509Extension(QOpcUaX509ExtensionData *other) +{ + data = other; +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509extension.h b/src/opcua/x509/qopcuax509extension.h new file mode 100644 index 0000000..6e502df --- /dev/null +++ b/src/opcua/x509/qopcuax509extension.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSION_H +#define QOPCUAX509EXTENSION_H + +#include <QtOpcUa/qopcuaglobal.h> +#include <QtCore/qshareddata.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaX509ExtensionData; +class Q_OPCUA_EXPORT QOpcUaX509Extension +{ +public: + QOpcUaX509Extension(); + QOpcUaX509Extension(const QOpcUaX509Extension &); + QOpcUaX509Extension &operator=(const QOpcUaX509Extension &); + bool operator==(const QOpcUaX509Extension &rhs) const; + virtual ~QOpcUaX509Extension(); + void setCritical(bool critical); + bool critical() const; + +protected: + QOpcUaX509Extension(QOpcUaX509ExtensionData*); + QOpcUaX509Extension(QSharedDataPointer<QOpcUaX509ExtensionData>); + QSharedDataPointer<QOpcUaX509ExtensionData> data; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAX509EXTENSION_H diff --git a/src/opcua/x509/qopcuax509extension_p.h b/src/opcua/x509/qopcuax509extension_p.h new file mode 100644 index 0000000..e945ffd --- /dev/null +++ b/src/opcua/x509/qopcuax509extension_p.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSION_P_H +#define QOPCUAX509EXTENSION_P_H + +#include <QtCore/qshareddata.h> + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +QT_BEGIN_NAMESPACE + +class QOpcUaX509ExtensionData : public QSharedData +{ +public: + bool critical = false; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAX509EXTENSION_P_H diff --git a/src/opcua/x509/qopcuax509extensionbasicconstraints.cpp b/src/opcua/x509/qopcuax509extensionbasicconstraints.cpp new file mode 100644 index 0000000..366288c --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionbasicconstraints.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509extensionbasicconstraints.h" +#include "qopcuax509extension_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509ExtensionBasicConstraints + \inmodule QtOpcUa + \since 5.14 + + \brief Class for X509 basic constraints + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. +*/ + +class QOpcUaX509ExtensionBasicConstraintsData : public QOpcUaX509ExtensionData +{ +public: + bool ca = false; + int pathLength = -1; +}; + +/*! + Constructs a default X509ExtensionBasicConstraints. +*/ +QOpcUaX509ExtensionBasicConstraints::QOpcUaX509ExtensionBasicConstraints() + : QOpcUaX509Extension(new QOpcUaX509ExtensionBasicConstraintsData) +{ +} + +/*! + Constructs a X509ExtensionBasicConstraints from \a rhs. +*/ +QOpcUaX509ExtensionBasicConstraints::QOpcUaX509ExtensionBasicConstraints(const QOpcUaX509ExtensionBasicConstraints &rhs) + : QOpcUaX509Extension(rhs.data) +{ +} + +/*! + Returns \c true if this X509ExtensionBasicConstraints has the same value as \a rhs. +*/ +bool QOpcUaX509ExtensionBasicConstraints::operator==(const QOpcUaX509ExtensionBasicConstraints &rhs) const +{ + return data->critical == rhs.data->critical; +} + +/*! + Destructs a X509ExtensionBasicConstraints. +*/ +QOpcUaX509ExtensionBasicConstraints::~QOpcUaX509ExtensionBasicConstraints() +{ +} + +/*! + Sets the values from \a rhs in this X509ExtensionBasicConstraints. +*/ +QOpcUaX509ExtensionBasicConstraints &QOpcUaX509ExtensionBasicConstraints::operator=(const QOpcUaX509ExtensionBasicConstraints &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Sets the flag, if the certificate's subject is a CA to \a value. +*/ +void QOpcUaX509ExtensionBasicConstraints::setCa(bool value) +{ + QOpcUaX509ExtensionBasicConstraintsData *d = static_cast<QOpcUaX509ExtensionBasicConstraintsData*>(data.data()); + d->ca = value; +} + +/*! + Returns the flag, if the certificate's subject is a CA. +*/ +bool QOpcUaX509ExtensionBasicConstraints::ca() const +{ + const QOpcUaX509ExtensionBasicConstraintsData *d = static_cast<const QOpcUaX509ExtensionBasicConstraintsData*>(data.data()); + return d->ca; +} + +/*! + Sets the validation path length of the certificate to \a length. +*/ +void QOpcUaX509ExtensionBasicConstraints::setPathLength(int length) +{ + QOpcUaX509ExtensionBasicConstraintsData *d = static_cast<QOpcUaX509ExtensionBasicConstraintsData*>(data.data()); + d->pathLength = length; +} + +/*! + Returns the validation path length of the certificate. +*/ +int QOpcUaX509ExtensionBasicConstraints::pathLength() const +{ + const QOpcUaX509ExtensionBasicConstraintsData *d = static_cast<const QOpcUaX509ExtensionBasicConstraintsData*>(data.data()); + return d->pathLength; +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509extensionbasicconstraints.h b/src/opcua/x509/qopcuax509extensionbasicconstraints.h new file mode 100644 index 0000000..eccbcd8 --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionbasicconstraints.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSIONBASICCONSTRAINTS_H +#define QOPCUAX509EXTENSIONBASICCONSTRAINTS_H + +#include "QtOpcUa/qopcuax509extension.h" +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +class QOpcUaX509ExtensionBasicConstraintsData; + +class Q_OPCUA_EXPORT QOpcUaX509ExtensionBasicConstraints : public QOpcUaX509Extension +{ +public: + QOpcUaX509ExtensionBasicConstraints(); + QOpcUaX509ExtensionBasicConstraints(const QOpcUaX509ExtensionBasicConstraints &); + QOpcUaX509ExtensionBasicConstraints &operator=(const QOpcUaX509ExtensionBasicConstraints &); + bool operator==(const QOpcUaX509ExtensionBasicConstraints &rhs) const; + ~QOpcUaX509ExtensionBasicConstraints(); + + void setCa(bool value); + bool ca() const; + + void setPathLength(int length); + int pathLength() const; +}; + +QT_END_NAMESPACE + +#endif // QOPCUAX509EXTENSIONBASICCONSTRAINTS_H diff --git a/src/opcua/x509/qopcuax509extensionextendedkeyusage.cpp b/src/opcua/x509/qopcuax509extensionextendedkeyusage.cpp new file mode 100644 index 0000000..cbe85d4 --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionextendedkeyusage.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509extensionextendedkeyusage.h" +#include "qopcuax509extension_p.h" +#include <QSet> + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509ExtensionExtendedKeyUsage + \inmodule QtOpcUa + \since 5.14 + + \brief Class for X509 extended key usage + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. +*/ + +/*! + \enum QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage + + Enum with entry types for X509ExtensionExtendedKeyUsage. + + \value TlsWebServerAuthentication + Permits TLS webserver Authentication + \value TlsWebClientAuthentication + Permits TLS client authentication + \value SignExecutableCode + Permits signature of executable code + \value EmailProtection + Permits signing emails +*/ + +class QOpcUaX509ExtensionExtendedKeyUsageData : public QOpcUaX509ExtensionData +{ +public: + QSet<QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage> keyUsage; +}; + +/*! + Constructs a X509ExtensionExtendedKeyUsage. +*/ +QOpcUaX509ExtensionExtendedKeyUsage::QOpcUaX509ExtensionExtendedKeyUsage() + : QOpcUaX509Extension(new QOpcUaX509ExtensionExtendedKeyUsageData) +{ + +} + +/*! + Constructs a X509ExtensionExtendedKeyUsage from \a rhs. +*/ +QOpcUaX509ExtensionExtendedKeyUsage::QOpcUaX509ExtensionExtendedKeyUsage(const QOpcUaX509ExtensionExtendedKeyUsage &rhs) + : QOpcUaX509Extension(rhs.data) +{ +} + +/*! + Returns \c true if this X509ExtensionExtendedKeyUsage has the same value as \a rhs. +*/ +bool QOpcUaX509ExtensionExtendedKeyUsage::operator==(const QOpcUaX509ExtensionExtendedKeyUsage &rhs) const +{ + return data->critical == rhs.data->critical; +} + +/*! + Destructs a X509ExtensionExtendedKeyUsage. +*/ +QOpcUaX509ExtensionExtendedKeyUsage::~QOpcUaX509ExtensionExtendedKeyUsage() +{ +} + +/*! + Sets the values from \a rhs in this X509ExtensionExtendedKeyUsage. +*/ +QOpcUaX509ExtensionExtendedKeyUsage &QOpcUaX509ExtensionExtendedKeyUsage::operator=(const QOpcUaX509ExtensionExtendedKeyUsage &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Sets the key usage flag in \a keyUsage to \a enable. +*/ +void QOpcUaX509ExtensionExtendedKeyUsage::setKeyUsage(KeyUsage keyUsage, bool enable) +{ + QOpcUaX509ExtensionExtendedKeyUsageData *d = static_cast<QOpcUaX509ExtensionExtendedKeyUsageData*>(data.data()); + + if (enable) + d->keyUsage.insert(keyUsage); + else + d->keyUsage.remove(keyUsage); +} + +/*! + Returns the key usage flag for \a keyUsage. +*/ +bool QOpcUaX509ExtensionExtendedKeyUsage::keyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage keyUsage) const +{ + const QOpcUaX509ExtensionExtendedKeyUsageData *d = static_cast<const QOpcUaX509ExtensionExtendedKeyUsageData*>(data.data()); + return d->keyUsage.contains(keyUsage); +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509extensionextendedkeyusage.h b/src/opcua/x509/qopcuax509extensionextendedkeyusage.h new file mode 100644 index 0000000..7fc7ce2 --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionextendedkeyusage.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSIONEXTENDEDKEYUSAGE_H +#define QOPCUAX509EXTENSIONEXTENDEDKEYUSAGE_H + +#include "QtOpcUa/qopcuax509extension.h" +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +class Q_OPCUA_EXPORT QOpcUaX509ExtensionExtendedKeyUsage : public QOpcUaX509Extension +{ +public: + enum class KeyUsage : uint { + TlsWebServerAuthentication, + TlsWebClientAuthentication, + SignExecutableCode, + EmailProtection, + }; + + QOpcUaX509ExtensionExtendedKeyUsage(); + QOpcUaX509ExtensionExtendedKeyUsage(const QOpcUaX509ExtensionExtendedKeyUsage &); + QOpcUaX509ExtensionExtendedKeyUsage &operator=(const QOpcUaX509ExtensionExtendedKeyUsage &); + bool operator==(const QOpcUaX509ExtensionExtendedKeyUsage &rhs) const; + ~QOpcUaX509ExtensionExtendedKeyUsage(); + + void setKeyUsage(KeyUsage keyUsage, bool enable = true); + bool keyUsage(KeyUsage) const; +}; + +inline uint qHash(const QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage &key) +{ + return ::qHash(static_cast<uint>(key)); +} + +QT_END_NAMESPACE + +#endif // QOPCUAX509EXTENSIONEXTENDEDKEYUSAGE_H diff --git a/src/opcua/x509/qopcuax509extensionkeyusage.cpp b/src/opcua/x509/qopcuax509extensionkeyusage.cpp new file mode 100644 index 0000000..f270899 --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionkeyusage.cpp @@ -0,0 +1,149 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509extensionkeyusage.h" +#include "qopcuax509extension_p.h" +#include <QSet> + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509ExtensionKeyUsage + \inmodule QtOpcUa + \since 5.14 + + \brief Class for X509 extended key usage + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. +*/ + +/*! + \enum QOpcUaX509ExtensionKeyUsage::KeyUsage + + Enum with entry types for X509ExtensionKeyUsage. + + \value DigitalSignature + Permits digital signatures + \value NonRepudiation + Permits non repudiation + \value KeyEncipherment + Permits key encipherment + \value DataEncipherment + Permits data encipherment + \value KeyAgreement + Permits key agreement + \value CertificateSigning + Permits certificate signing + \value CrlSigning + Permits CRL signing + \value EnciptherOnly + Restricts to encipherment only + \value DecipherOnly + Restricts to decipher only +*/ + +class QOpcUaX509ExtensionKeyUsageData : public QOpcUaX509ExtensionData +{ +public: + QSet<QOpcUaX509ExtensionKeyUsage::KeyUsage> keyUsage; +}; + +/*! + Constructs a X509ExtensionKeyUsage. +*/ +QOpcUaX509ExtensionKeyUsage::QOpcUaX509ExtensionKeyUsage() + : QOpcUaX509Extension(new QOpcUaX509ExtensionKeyUsageData) +{ +} + +/*! + Constructs a X509ExtensionKeyUsage from \a rhs. +*/ +QOpcUaX509ExtensionKeyUsage::QOpcUaX509ExtensionKeyUsage(const QOpcUaX509ExtensionKeyUsage &rhs) + : QOpcUaX509Extension(rhs.data) +{ +} + +/*! + Returns \c true if this X509ExtensionKeyUsage has the same value as \a rhs. +*/ +bool QOpcUaX509ExtensionKeyUsage::operator==(const QOpcUaX509ExtensionKeyUsage &rhs) const +{ + return data->critical == rhs.data->critical; +} + +/*! + Destructs a X509ExtensionKeyUsage. +*/ +QOpcUaX509ExtensionKeyUsage::~QOpcUaX509ExtensionKeyUsage() +{ +} + +/*! + Sets the values from \a rhs in this X509ExtensionKeyUsage. +*/ +QOpcUaX509ExtensionKeyUsage &QOpcUaX509ExtensionKeyUsage::operator=(const QOpcUaX509ExtensionKeyUsage &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Sets the key usage flag in \a keyUsage to \a enable. +*/ +void QOpcUaX509ExtensionKeyUsage::setKeyUsage(KeyUsage keyUsage, bool enable) +{ + QOpcUaX509ExtensionKeyUsageData *d = static_cast<QOpcUaX509ExtensionKeyUsageData*>(data.data()); + + if (enable) + d->keyUsage.insert(keyUsage); + else + d->keyUsage.remove(keyUsage); +} + +/*! + Returns the key usage flag for \a keyUsage. +*/ +bool QOpcUaX509ExtensionKeyUsage::keyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage keyUsage) const +{ + const QOpcUaX509ExtensionKeyUsageData *d = static_cast<const QOpcUaX509ExtensionKeyUsageData*>(data.data()); + return d->keyUsage.contains(keyUsage); +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509extensionkeyusage.h b/src/opcua/x509/qopcuax509extensionkeyusage.h new file mode 100644 index 0000000..7e0586c --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionkeyusage.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSIONKEYUSAGE_H +#define QOPCUAX509EXTENSIONKEYUSAGE_H + +#include "QtOpcUa/qopcuax509extension.h" +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +class Q_OPCUA_EXPORT QOpcUaX509ExtensionKeyUsage : public QOpcUaX509Extension +{ +public: + enum class KeyUsage : uint { + DigitalSignature, + NonRepudiation, + KeyEncipherment, + DataEncipherment, + KeyAgreement, + CertificateSigning, + CrlSigning, + EnciptherOnly, + DecipherOnly + }; + + QOpcUaX509ExtensionKeyUsage(); + QOpcUaX509ExtensionKeyUsage(const QOpcUaX509ExtensionKeyUsage &); + QOpcUaX509ExtensionKeyUsage &operator=(const QOpcUaX509ExtensionKeyUsage &); + bool operator==(const QOpcUaX509ExtensionKeyUsage &rhs) const; + ~QOpcUaX509ExtensionKeyUsage(); + + void setKeyUsage(KeyUsage keyUsage, bool enable = true); + bool keyUsage(KeyUsage) const; +}; + +inline uint qHash(const QOpcUaX509ExtensionKeyUsage::KeyUsage &key) +{ + return ::qHash(static_cast<uint>(key)); +} + +QT_END_NAMESPACE + +#endif // QOPCUAX509EXTENSIONKEYUSAGE_H diff --git a/src/opcua/x509/qopcuax509extensionsubjectalternativename.cpp b/src/opcua/x509/qopcuax509extensionsubjectalternativename.cpp new file mode 100644 index 0000000..86bad2e --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionsubjectalternativename.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qopcuax509extensionsubjectalternativename.h" +#include "qopcuax509extension_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QOpcUaX509ExtensionSubjectAlternativeName + \inmodule QtOpcUa + \since 5.14 + + \brief Class for an X509 subject alternative name + + This class is currently available as a Technology Preview, and therefore the API + and functionality provided by the class may be subject to change at any time without + prior notice. +*/ + +/*! + \enum QOpcUaX509ExtensionSubjectAlternativeName::Type + + Enum with entry types for subject alternative name. + + \value Email + Entry type for an email address + \value URI + Entry type for an URI + \value DNS + Entry type for DNS + \value IP + Entry type for an IP address +*/ + +class QOpcUaX509ExtensionSubjectAlternativeNameData : public QOpcUaX509ExtensionData +{ +public: + QVector <QPair<QOpcUaX509ExtensionSubjectAlternativeName::Type, QString>> entries; +}; + +/*! + Constructs a X509ExtensionSubjectAlternativeName. +*/ +QOpcUaX509ExtensionSubjectAlternativeName::QOpcUaX509ExtensionSubjectAlternativeName() + : QOpcUaX509Extension(new QOpcUaX509ExtensionSubjectAlternativeNameData) +{ +} + +/*! + Constructs a X509ExtensionSubjectAlternativeName from \a rhs. +*/ +QOpcUaX509ExtensionSubjectAlternativeName::QOpcUaX509ExtensionSubjectAlternativeName(const QOpcUaX509ExtensionSubjectAlternativeName &rhs) + : QOpcUaX509Extension(rhs.data) +{ +} + +/*! + Returns \c true if this X509ExtensionSubjectAlternativeName has the same value as \a rhs. +*/ +bool QOpcUaX509ExtensionSubjectAlternativeName::operator==(const QOpcUaX509ExtensionSubjectAlternativeName &rhs) const +{ + return data->critical == rhs.data->critical; +} + +/*! + Destructs a X509ExtensionSubjectAlternativeName. +*/ +QOpcUaX509ExtensionSubjectAlternativeName::~QOpcUaX509ExtensionSubjectAlternativeName() +{ +} + +/*! + Sets the values from \a rhs in this X509ExtensionSubjectAlternativeName. +*/ +QOpcUaX509ExtensionSubjectAlternativeName &QOpcUaX509ExtensionSubjectAlternativeName::operator=(const QOpcUaX509ExtensionSubjectAlternativeName &rhs) +{ + if (this != &rhs) + data.operator=(rhs.data); + return *this; +} + +/*! + Adds an entry of type \a type with content \a value. +*/ +void QOpcUaX509ExtensionSubjectAlternativeName::addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type type, const QString &value) +{ + QOpcUaX509ExtensionSubjectAlternativeNameData *d = static_cast<QOpcUaX509ExtensionSubjectAlternativeNameData*>(data.data()); + d->entries.append(qMakePair(type, value)); +} + +/*! + Returns the vector of entries. +*/ +const QVector<QPair<QOpcUaX509ExtensionSubjectAlternativeName::Type, QString>> &QOpcUaX509ExtensionSubjectAlternativeName::entries() const +{ + const QOpcUaX509ExtensionSubjectAlternativeNameData *d = static_cast<const QOpcUaX509ExtensionSubjectAlternativeNameData*>(data.data()); + return d->entries; +} + +QT_END_NAMESPACE diff --git a/src/opcua/x509/qopcuax509extensionsubjectalternativename.h b/src/opcua/x509/qopcuax509extensionsubjectalternativename.h new file mode 100644 index 0000000..4f13117 --- /dev/null +++ b/src/opcua/x509/qopcuax509extensionsubjectalternativename.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QOPCUAX509EXTENSIONSUBJECTALTERNATIVENAME_H +#define QOPCUAX509EXTENSIONSUBJECTALTERNATIVENAME_H + +#include "QtOpcUa/qopcuax509extension.h" +#include <QtOpcUa/qopcuaglobal.h> + +QT_BEGIN_NAMESPACE + +// OID 2.5.29.17 +class Q_OPCUA_EXPORT QOpcUaX509ExtensionSubjectAlternativeName : public QOpcUaX509Extension +{ +public: + enum class Type { + Email, + URI, + DNS, + IP + }; + + QOpcUaX509ExtensionSubjectAlternativeName(); + QOpcUaX509ExtensionSubjectAlternativeName(const QOpcUaX509ExtensionSubjectAlternativeName &); + QOpcUaX509ExtensionSubjectAlternativeName &operator=(const QOpcUaX509ExtensionSubjectAlternativeName &); + bool operator==(const QOpcUaX509ExtensionSubjectAlternativeName &rhs) const; + ~QOpcUaX509ExtensionSubjectAlternativeName(); + + void addEntry(Type type, const QString &value); + const QVector<QPair<Type, QString>> &entries() const; +}; + +QT_END_NAMESPACE +#endif // QOPCUAX509EXTENSIONSUBJECTALTERNATIVENAME_H diff --git a/src/opcua/x509/qopcuax509utils.cpp b/src/opcua/x509/qopcuax509utils.cpp new file mode 100644 index 0000000..74eb5d4 --- /dev/null +++ b/src/opcua/x509/qopcuax509utils.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qopcuax509utils_p.h" +#include "openssl_symbols_p.h" + +QT_BEGIN_NAMESPACE + +QString getOpenSslError() +{ + char errorBuf[1024]; + errorBuf[0] = 0; + q_ERR_error_string_n(q_ERR_get_error(), errorBuf, sizeof(errorBuf)); + return QString::fromLatin1(errorBuf); +} + +QT_END_NAMESPACE + diff --git a/src/opcua/x509/qopcuax509utils_p.h b/src/opcua/x509/qopcuax509utils_p.h new file mode 100644 index 0000000..4f9f00e --- /dev/null +++ b/src/opcua/x509/qopcuax509utils_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QOPCUAX509UTILS_H +#define QOPCUAX509UTILS_H + +#include <functional> +#include <QString> + +QT_BEGIN_NAMESPACE + +template <typename T> +class Deleter +{ +public: + Deleter(T *data, std::function<void(T *value)> f) + : m_data(data) + , m_function(f) + { + } + ~Deleter() + { + if (m_data) + m_function(m_data); + } + void release() + { + m_data = nullptr; + m_function = nullptr; + } +private: + T *m_data {nullptr}; + std::function<void(T *attribute)> m_function; +}; + +QString getOpenSslError(); + +QT_END_NAMESPACE + +#endif // QOPCUAX509UTILS_H diff --git a/src/opcua/x509/qsslsocket_openssl11_symbols_p.h b/src/opcua/x509/qsslsocket_openssl11_symbols_p.h new file mode 100644 index 0000000..cac3999 --- /dev/null +++ b/src/opcua/x509/qsslsocket_openssl11_symbols_p.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2016 Richard J. Moore <rich@kde.org> +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + +#ifndef QSSLSOCKET_OPENSSL11_SYMBOLS_P_H +#define QSSLSOCKET_OPENSSL11_SYMBOLS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// Note: this file does not have QT_BEGIN_NAMESPACE/QT_END_NAMESPACE, it's done +// in qsslsocket_openssl_symbols_p.h. + +#ifndef OPENSSL_SYMBOLS_P_H +#error "You are not supposed to use this header file, include openssl_symbols_p.h instead" +#endif + +#include <openssl/x509.h> + +const unsigned char * q_ASN1_STRING_get0_data(const ASN1_STRING *x); + +Q_AUTOTEST_EXPORT BIO *q_BIO_new(const BIO_METHOD *a); +Q_AUTOTEST_EXPORT const BIO_METHOD *q_BIO_s_mem(); + +int q_DSA_bits(DSA *a); +int q_EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *c); +int q_EVP_PKEY_base_id(EVP_PKEY *a); +int q_RSA_bits(RSA *a); +Q_AUTOTEST_EXPORT int q_OPENSSL_sk_num(OPENSSL_STACK *a); +Q_AUTOTEST_EXPORT void q_OPENSSL_sk_pop_free(OPENSSL_STACK *a, void (*b)(void *)); +Q_AUTOTEST_EXPORT OPENSSL_STACK *q_OPENSSL_sk_new_null(); +Q_AUTOTEST_EXPORT void q_OPENSSL_sk_push(OPENSSL_STACK *st, void *data); +Q_AUTOTEST_EXPORT void q_OPENSSL_sk_free(OPENSSL_STACK *a); +Q_AUTOTEST_EXPORT void * q_OPENSSL_sk_value(OPENSSL_STACK *a, int b); +int q_SSL_session_reused(SSL *a); +unsigned long q_SSL_CTX_set_options(SSL_CTX *ctx, unsigned long op); +int q_OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +size_t q_SSL_get_client_random(SSL *a, unsigned char *out, size_t outlen); +size_t q_SSL_SESSION_get_master_key(const SSL_SESSION *session, unsigned char *out, size_t outlen); +int q_CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); +const SSL_METHOD *q_TLS_method(); +const SSL_METHOD *q_TLS_client_method(); +const SSL_METHOD *q_TLS_server_method(); +ASN1_TIME *q_X509_getm_notBefore(X509 *a); +ASN1_TIME *q_X509_getm_notAfter(X509 *a); + +Q_AUTOTEST_EXPORT void q_X509_up_ref(X509 *a); +long q_X509_get_version(X509 *a); +EVP_PKEY *q_X509_get_pubkey(X509 *a); +void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify_cb); +STACK_OF(X509) *q_X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx); +void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); +int q_DH_bits(DH *dh); + +# define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \ + | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL) + +#define q_SKM_sk_num(type, st) ((int (*)(const STACK_OF(type) *))q_OPENSSL_sk_num)(st) +#define q_SKM_sk_value(type, st,i) ((type * (*)(const STACK_OF(type) *, int))q_OPENSSL_sk_value)(st, i) + +#define q_OPENSSL_add_all_algorithms_conf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS \ + | OPENSSL_INIT_LOAD_CONFIG, NULL) +#define q_OPENSSL_add_all_algorithms_noconf() q_OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS \ + | OPENSSL_INIT_ADD_ALL_DIGESTS, NULL) + +// We resolve q_sk_ functions, but use q_OPENSSL_sk_ macros in code to reduce +// the amount of #ifdefs and for confusing developers. +OPENSSL_STACK *q_sk_new_null(); +#define q_OPENSSL_sk_new_null() q_sk_new_null() +#define q_sk_X509_EXTENSION_new_null() \ + ((OPENSSL_STACK *)q_sk_new_null()) + +void q_sk_push(OPENSSL_STACK *st, void *data); + +#define q_sk_X509_EXTENSION_push(st, val) \ + q_OPENSSL_sk_push((st), (val)) +#define q_sk_X509_EXTENSION_pop_free(st, free_func) \ + q_OPENSSL_sk_pop_free((st), (free_func)) + + +int q_OPENSSL_init_crypto(uint64_t opts, const OPENSSL_INIT_SETTINGS *settings); +void q_CRYPTO_free(void *str, const char *file, int line); + +long q_OpenSSL_version_num(); +const char *q_OpenSSL_version(int type); + +unsigned long q_SSL_SESSION_get_ticket_lifetime_hint(const SSL_SESSION *session); +unsigned long q_SSL_set_options(SSL *s, unsigned long op); + +#ifdef TLS1_3_VERSION +int q_SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); +#endif + +void q_BIO_set_data(BIO *a, void *ptr); +void *q_BIO_get_data(BIO *a); +void q_BIO_set_init(BIO *a, int init); +int q_BIO_get_shutdown(BIO *a); +void q_BIO_set_shutdown(BIO *a, int shut); + +EVP_PKEY_CTX* q_EVP_PKEY_CTX_new_id(int id, ENGINE *e); +void q_EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); +int q_EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx); + +int q_RSA_pkey_ctx_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void *p2); +#define q_EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) \ + q_RSA_pkey_ctx_ctrl(ctx, EVP_PKEY_OP_KEYGEN, \ + EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL) + +int q_EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **pkey); + +#define q_SSL_CTX_set_min_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MIN_PROTO_VERSION, version, nullptr) + +#define q_SSL_CTX_set_max_proto_version(ctx, version) \ + q_SSL_CTX_ctrl(ctx, SSL_CTRL_SET_MAX_PROTO_VERSION, version, nullptr) + +#endif diff --git a/src/opcua/x509/qsslsocket_opensslpre11_symbols_p.h b/src/opcua/x509/qsslsocket_opensslpre11_symbols_p.h new file mode 100644 index 0000000..ee41ad9 --- /dev/null +++ b/src/opcua/x509/qsslsocket_opensslpre11_symbols_p.h @@ -0,0 +1,196 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** In addition, as a special exception, the copyright holders listed above give +** permission to link the code of its release of Qt with the OpenSSL project's +** "OpenSSL" library (or modified versions of the "OpenSSL" library that use the +** same license as the original version), and distribute the linked executables. +** +** You must comply with the GNU General Public License version 2 in all +** respects for all of the code used other than the "OpenSSL" code. If you +** modify this file, you may extend this exception to your version of the file, +** but you are not obligated to do so. If you do not wish to do so, delete +** this exception statement from your version of this file. +** +****************************************************************************/ + + +#ifndef QSSLSOCKET_OPENSSLPRE11_SYMBOLS_P_H +#define QSSLSOCKET_OPENSSLPRE11_SYMBOLS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +// Note: this file does not have QT_BEGIN_NAMESPACE/QT_END_NAMESPACE, it's done +// in qsslsocket_openssl_symbols_p.h. + +#ifndef OPENSSL_SYMBOLS_P_H +#error "You are not supposed to use this header file, include openssl_symbols_p.h instead" +#endif + +unsigned char * q_ASN1_STRING_data(ASN1_STRING *a); +BIO *q_BIO_new_file(const char *filename, const char *mode); +void q_ERR_clear_error(); +Q_AUTOTEST_EXPORT BIO *q_BIO_new(BIO_METHOD *a); +Q_AUTOTEST_EXPORT BIO_METHOD *q_BIO_s_mem(); +int q_CRYPTO_num_locks(); +void q_CRYPTO_set_locking_callback(void (*a)(int, int, const char *, int)); +void q_CRYPTO_set_id_callback(unsigned long (*a)()); +void q_CRYPTO_free(void *a); +unsigned long q_ERR_peek_last_error(); +void q_ERR_free_strings(); +void q_EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a); +void q_EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a); + +typedef _STACK STACK; + +// The typedef we use to make our pre 1.1 code look more like 1.1 (less ifdefs). +typedef STACK OPENSSL_STACK; + +// We resolve q_sk_ functions, but use q_OPENSSL_sk_ macros in code to reduce +// the amount of #ifdefs and for confusing developers. +int q_sk_num(STACK *a); +#define q_OPENSSL_sk_num(a) q_sk_num(a) +void q_sk_pop_free(STACK *a, void (*b)(void *)); +#define q_OPENSSL_sk_pop_free(a, b) (a, b) +STACK *q_sk_new_null(); +#define q_OPENSSL_sk_new_null() q_sk_new_null() + +#define q_sk_X509_EXTENSION_new_null() \ + ((STACK_OF(X509_EXTENSION) *)q_sk_new_null()) + +#define q_SKM_sk_push(type, st, val) \ + q_OPENSSL_sk_push(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, val)) + +#define q_sk_X509_EXTENSION_push(st, val) \ + q_SKM_sk_push(X509_EXTENSION, (st), (val)) +#define q_sk_X509_EXTENSION_pop_free(st, free_func) \ + q_OPENSSL_sk_pop_free(CHECKED_STACK_OF(X509_EXTENSION, st), (free_func)) + +void q_sk_free(STACK *a); + +// Just a name alias (not a function call expression) since in code we take an +// address of this: +#define q_OPENSSL_sk_free q_sk_free + +void *q_sk_value(STACK *a, int b); +void q_sk_push(STACK *st, void *data); + +#define q_OPENSSL_sk_value(a, b) q_sk_value(a, b) +#define q_OPENSSL_sk_push(st, data) q_sk_push(st, data) + +SSL_CTX *q_SSL_CTX_new(const SSL_METHOD *a); + +int q_SSL_library_init(); +void q_SSL_load_error_strings(); + +#if OPENSSL_VERSION_NUMBER >= 0x10001000L +int q_SSL_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); +#endif + +const SSL_METHOD *q_SSLv23_client_method(); +const SSL_METHOD *q_TLSv1_client_method(); +const SSL_METHOD *q_TLSv1_1_client_method(); +const SSL_METHOD *q_TLSv1_2_client_method(); +const SSL_METHOD *q_SSLv23_server_method(); +const SSL_METHOD *q_TLSv1_server_method(); +const SSL_METHOD *q_TLSv1_1_server_method(); +const SSL_METHOD *q_TLSv1_2_server_method(); + +STACK_OF(X509) *q_X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx); + +#ifdef SSLEAY_MACROS +int q_i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); +int q_i2d_RSAPrivateKey(const RSA *a, unsigned char **pp); +RSA *q_d2i_RSAPrivateKey(RSA **a, unsigned char **pp, long length); +DSA *q_d2i_DSAPrivateKey(DSA **a, unsigned char **pp, long length); +#define q_PEM_read_bio_RSAPrivateKey(bp, x, cb, u) \ + (RSA *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_RSAPrivateKey, PEM_STRING_RSA, bp, (void **)x, cb, u) +#define q_PEM_read_bio_DSAPrivateKey(bp, x, cb, u) \ + (DSA *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_DSAPrivateKey, PEM_STRING_DSA, bp, (void **)x, cb, u) +#define q_PEM_write_bio_RSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_RSAPrivateKey,PEM_STRING_RSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_write_bio_DSAPrivateKey(bp,x,enc,kstr,klen,cb,u) \ + PEM_ASN1_write_bio((int (*)(void*, unsigned char**))q_i2d_DSAPrivateKey,PEM_STRING_DSA,\ + bp,(char *)x,enc,kstr,klen,cb,u) +#define q_PEM_read_bio_DHparams(bp, dh, cb, u) \ + (DH *)q_PEM_ASN1_read_bio( \ + (void *(*)(void**, const unsigned char**, long int))q_d2i_DHparams, PEM_STRING_DHPARAMS, bp, (void **)x, cb, u) +#endif // SSLEAY_MACROS + +#define q_SSL_CTX_set_options(ctx,op) q_SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL) +#define q_SSL_set_options(ssl,op) q_SSL_ctrl((ssl),SSL_CTRL_OPTIONS,(op),nullptr) +#define q_SKM_sk_num(type, st) ((int (*)(const STACK_OF(type) *))q_sk_num)(st) +#define q_SKM_sk_value(type, st,i) ((type * (*)(const STACK_OF(type) *, int))q_sk_value)(st, i) +#define q_X509_getm_notAfter(x) X509_get_notAfter(x) +#define q_X509_getm_notBefore(x) X509_get_notBefore(x) + +// "Forward compatibility" with OpenSSL 1.1 (to save on #if-ery elsewhere): +#define q_X509_get_version(x509) q_ASN1_INTEGER_get((x509)->cert_info->version) +#define q_ASN1_STRING_get0_data(x) q_ASN1_STRING_data(x) +#define q_EVP_PKEY_base_id(pkey) ((pkey)->type) +#define q_X509_get_pubkey(x509) q_X509_PUBKEY_get((x509)->cert_info->key) +#define q_SSL_SESSION_get_ticket_lifetime_hint(s) ((s)->tlsext_tick_lifetime_hint) +#define q_RSA_bits(rsa) q_BN_num_bits((rsa)->n) +#define q_DSA_bits(dsa) q_BN_num_bits((dsa)->p) +#define q_DH_bits(dsa) q_BN_num_bits((dh)->p) +#define q_X509_STORE_set_verify_cb(s,c) X509_STORE_set_verify_cb_func((s),(c)) + +char *q_CONF_get1_default_config_file(); +void q_OPENSSL_add_all_algorithms_noconf(); +void q_OPENSSL_add_all_algorithms_conf(); + +long q_SSLeay(); +const char *q_SSLeay_version(int type); + +#endif // QSSLSOCKET_OPENSSL_PRE11_SYMBOLS_P_H diff --git a/src/opcua/x509/x509.pri b/src/opcua/x509/x509.pri new file mode 100644 index 0000000..efcb13c --- /dev/null +++ b/src/opcua/x509/x509.pri @@ -0,0 +1,33 @@ +PUBLIC_HEADERS += \ + $$PWD/qopcuakeypair.h \ + $$PWD/qopcuax509certificatesigningrequest.h \ + $$PWD/qopcuax509distinguishedname.h \ + $$PWD/qopcuax509extensionextendedkeyusage.h \ + $$PWD/qopcuax509extension.h \ + $$PWD/qopcuax509extensionkeyusage.h \ + $$PWD/qopcuax509extensionsubjectalternativename.h \ + $$PWD/qopcuax509extensionbasicconstraints.h \ + +PRIVATE_HEADERS += \ + $$PWD/qopcuakeypair_p.h \ + $$PWD/openssl_symbols_p.h \ + $$PWD/qsslsocket_openssl11_symbols_p.h \ + $$PWD/qsslsocket_opensslpre11_symbols_p.h \ + $$PWD/qopcuax509certificatesigningrequest_p.h \ + $$PWD/qopcuax509utils_p.h \ + $$PWD/qopcuax509extension_p.h \ + +SOURCES += \ + $$PWD/qopcuax509certificatesigningrequest.cpp \ + $$PWD/qopcuax509distinguishedname.cpp \ + $$PWD/qopcuax509extensionextendedkeyusage.cpp \ + $$PWD/qopcuax509extension.cpp \ + $$PWD/qopcuax509extensionkeyusage.cpp \ + $$PWD/qopcuax509extensionsubjectalternativename.cpp \ + $$PWD/qopcuax509extensionbasicconstraints.cpp \ + $$PWD/openssl_symbols.cpp \ + $$PWD/qopcuakeypair.cpp \ + $$PWD/qopcuakeypair_openssl.cpp \ + $$PWD/qopcuax509certificatesigningrequest_openssl.cpp \ + $$PWD/qopcuax509utils.cpp \ + diff --git a/src/plugins/opcua/open62541/qopen62541utils.h b/src/plugins/opcua/open62541/qopen62541utils.h index fd13219..6990912 100644 --- a/src/plugins/opcua/open62541/qopen62541utils.h +++ b/src/plugins/opcua/open62541/qopen62541utils.h @@ -39,7 +39,7 @@ #include "qopen62541.h" -#include <QtCore/qstring.h> +#include <QString> #include <functional> diff --git a/src/plugins/opcua/uacpp/quacppbackend.cpp b/src/plugins/opcua/uacpp/quacppbackend.cpp index 1681438..627cceb 100644 --- a/src/plugins/opcua/uacpp/quacppbackend.cpp +++ b/src/plugins/opcua/uacpp/quacppbackend.cpp @@ -630,7 +630,7 @@ void UACppAsyncBackend::callMethod(quint64 handle, const UaNodeId &objectId, con UaStatus status = m_nativeSession->call(settings, in, out); if (status.isBad()) - qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Calling method failed"; + qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Calling method" << methodId.toFullString().toUtf8() << "failed"; if (out.callResult.isBad()) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Method call returned a failure"; diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 9d8b7db..82a44df 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -1,10 +1,11 @@ TEMPLATE = subdirs SUBDIRS += qopcuaclient connection clientSetupInCpp security -QT_FOR_CONFIG += opcua-private +QT_FOR_CONFIG += opcua-private core-private # only build declarative tests if at least one backend was built qtHaveModule(qmltest):qtConfig(open62541)|qtConfig(uacpp) { SUBDIRS += declarative } +qtConfig(ssl):!darwin:!winrt: SUBDIRS += x509 diff --git a/tests/auto/x509/tst_x509.cpp b/tests/auto/x509/tst_x509.cpp new file mode 100644 index 0000000..d00c3d9 --- /dev/null +++ b/tests/auto/x509/tst_x509.cpp @@ -0,0 +1,274 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtOpcUa/QOpcUaProvider> +#include <QtOpcUa/QOpcUaKeyPair> + +#include <QtCore/QCoreApplication> +#include <QtCore/QScopedPointer> +#include <QOpcUaX509CertificateSigningRequest> +#include <QOpcUaX509ExtensionSubjectAlternativeName> +#include <QOpcUaX509ExtensionBasicConstraints> +#include <QOpcUaX509ExtensionKeyUsage> +#include <QOpcUaX509ExtensionExtendedKeyUsage> + +#include <QtTest/QSignalSpy> +#include <QtTest/QtTest> + +#define defineDataMethod(name) void name()\ +{\ + QTest::addColumn<QString>("backend");\ + for (auto backend : m_backends) {\ + const QString rowName = QString("%1").arg(backend); \ + QTest::newRow(rowName.toLatin1().constData()) << backend ; \ + }\ +} + +class Tst_QOpcUaSecurity: public QObject +{ + Q_OBJECT + +public: + Tst_QOpcUaSecurity(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + defineDataMethod(keyPairs_data) + void keyPairs(); + + defineDataMethod(certificateSigningRequest_data) + void certificateSigningRequest(); + +private: + QStringList m_backends; + QOpcUaProvider m_opcUa; +}; + +QByteArray textifyCertificateRequest(const QByteArray &data) +{ + QProcess p; + p.start("openssl", QStringList {"req", "-text", "-noout"}); + p.waitForStarted(); + p.write(data); + p.closeWriteChannel(); + p.waitForFinished(); + return p.readAllStandardOutput(); +} + +QByteArray textifyCertificate(const QByteArray &data) +{ + QProcess p; + p.start("openssl", QStringList {"x509", "-text", "-noout"}); + p.waitForStarted(); + p.write(data); + p.closeWriteChannel(); + p.waitForFinished(); + return p.readAllStandardOutput(); +} + +QByteArray asn1dump(const QByteArray &data) +{ + QProcess p; + p.start("openssl", QStringList {"asn1parse", "-inform","PEM"}); + p.waitForStarted(); + p.write(data); + p.closeWriteChannel(); + p.waitForFinished(); + return p.readAllStandardOutput(); +} + +Tst_QOpcUaSecurity::Tst_QOpcUaSecurity() +{ + m_backends = QOpcUaProvider::availableBackends(); +} + +void Tst_QOpcUaSecurity::initTestCase() +{ +} + +void Tst_QOpcUaSecurity::keyPairs() +{ + QFETCH(QString, backend); + + QOpcUaKeyPair key; + QOpcUaKeyPair loadedKey; + QByteArray byteArray; + + QVERIFY(key.hasPrivateKey() == false); + + // Generate key + key.generateRsaKey(QOpcUaKeyPair::RsaKeyStrength::Bits1024); + QVERIFY(key.hasPrivateKey() == true); + + // Export public key + byteArray = key.publicKeyToByteArray(); + QVERIFY(byteArray.startsWith("-----BEGIN PUBLIC KEY-----\n")); + QVERIFY(byteArray.endsWith("-----END PUBLIC KEY-----\n")); + + // Load public key + QVERIFY(loadedKey.loadFromPemData(byteArray)); + QVERIFY(loadedKey.hasPrivateKey() == false); + + // Check unencrypted PEM export + byteArray = key.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Unencrypted, QString()); + QVERIFY(byteArray.startsWith("-----BEGIN PRIVATE KEY-----\n")); + QVERIFY(byteArray.endsWith("-----END PRIVATE KEY-----\n")); + + // Load private key from PEM data + QSignalSpy passwordSpy(&loadedKey, SIGNAL(passphraseNeeded(QString&,int,bool))); + + QVERIFY(loadedKey.loadFromPemData(byteArray)); + QVERIFY(loadedKey.hasPrivateKey() == true); + QCOMPARE(passwordSpy.count(), 0); + QCOMPARE(loadedKey.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Unencrypted, QString()), byteArray); + + // Check encrypted PEM export + byteArray = key.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Aes128Cbc, QString("password")); + QVERIFY(byteArray.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----\n")); + QVERIFY(byteArray.endsWith("-----END ENCRYPTED PRIVATE KEY-----\n")); + QCOMPARE(passwordSpy.count(), 0); + + // Setup password callback + QString passphraseToReturn; + connect(&loadedKey, &QOpcUaKeyPair::passphraseNeeded, this, [&passphraseToReturn](QString &passphrase, int maximumLength, bool writeOperation){ + Q_UNUSED(maximumLength); + qDebug() << "Requested a passphrase for" << (writeOperation ? "write":"read") << "operation"; + passphrase = passphraseToReturn; + }); + + // Load key with wrong password + qDebug() << "Trying to decrypt with wrong password; will cause an error"; + passphraseToReturn = "WrongPassword"; + QVERIFY(!loadedKey.loadFromPemData(byteArray)); + QCOMPARE(passwordSpy.count(), 1); + QVERIFY(loadedKey.hasPrivateKey() == false); + + // Load key with right password + qDebug() << "Trying to decrypt with right password; will cause no error"; + passphraseToReturn = "password"; + QVERIFY(loadedKey.loadFromPemData(byteArray)); + QCOMPARE(passwordSpy.count(), 2); + QCOMPARE(loadedKey.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Unencrypted, QString()), + key.privateKeyToByteArray(QOpcUaKeyPair::Cipher::Unencrypted, QString())); + QVERIFY(loadedKey.hasPrivateKey() == true); +} + +void Tst_QOpcUaSecurity::certificateSigningRequest() +{ + QFETCH(QString, backend); + + QOpcUaKeyPair key; + + // Generate key + key.generateRsaKey(QOpcUaKeyPair::RsaKeyStrength::Bits1024); + QVERIFY(key.hasPrivateKey() == true); + + QOpcUaX509CertificateSigningRequest csr; + + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CommonName, "QtOpcUaViewer"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, "DE"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, "Berlin"); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, "The Qt Company"); + csr.setSubject(dn); + + QOpcUaX509ExtensionSubjectAlternativeName *san = new QOpcUaX509ExtensionSubjectAlternativeName; + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, "foo.com"); + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::DNS, "bla.com"); + san->addEntry(QOpcUaX509ExtensionSubjectAlternativeName::Type::URI, "urn:foo.com:The%20Qt%20Company:QtOpcUaViewer"); + san->setCritical(true); + csr.addExtension(san); + + QOpcUaX509ExtensionBasicConstraints *bc = new QOpcUaX509ExtensionBasicConstraints; + bc->setCa(false); + bc->setCritical(true); + csr.addExtension(bc); + + QOpcUaX509ExtensionKeyUsage *ku = new QOpcUaX509ExtensionKeyUsage; + ku->setCritical(true); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DigitalSignature); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::NonRepudiation); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::KeyEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::DataEncipherment); + ku->setKeyUsage(QOpcUaX509ExtensionKeyUsage::KeyUsage::CertificateSigning); + csr.addExtension(ku); + + QOpcUaX509ExtensionExtendedKeyUsage *eku = new QOpcUaX509ExtensionExtendedKeyUsage; + eku->setCritical(true); + eku->setKeyUsage(QOpcUaX509ExtensionExtendedKeyUsage::KeyUsage::EmailProtection); + csr.addExtension(eku); + + QByteArray csrData = csr.createRequest(key); + qDebug() << csrData; + QVERIFY(csrData.startsWith("-----BEGIN CERTIFICATE REQUEST-----\n")); + QVERIFY(csrData.endsWith("\n-----END CERTIFICATE REQUEST-----\n")); + qDebug().noquote() << textifyCertificateRequest(csrData); + qDebug().noquote() << asn1dump(csrData); + + QByteArray certData = csr.createSelfSignedCertificate(key); + qDebug() << certData; + QVERIFY(certData.startsWith("-----BEGIN CERTIFICATE-----\n")); + QVERIFY(certData.endsWith("\n-----END CERTIFICATE-----\n")); + qDebug().noquote() << textifyCertificate(certData); + qDebug().noquote() << asn1dump(certData); +} + +void Tst_QOpcUaSecurity::cleanupTestCase() +{ +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QTEST_SET_MAIN_SOURCE_PATH + + // run tests for all available backends + QStringList availableBackends = QOpcUaProvider::availableBackends(); + if (availableBackends.empty()) { + qDebug("No OPCUA backends found, skipping tests."); + return EXIT_SUCCESS; + } + + Tst_QOpcUaSecurity tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_x509.moc" + diff --git a/tests/auto/x509/x509.pro b/tests/auto/x509/x509.pro new file mode 100644 index 0000000..b1f1fc1 --- /dev/null +++ b/tests/auto/x509/x509.pro @@ -0,0 +1,8 @@ +TARGET = tst_x509 + +QT += testlib opcua network +QT -= gui +CONFIG += testcase + +SOURCES += \ + tst_x509.cpp diff --git a/tests/manual/gds/gds.pro b/tests/manual/gds/gds.pro new file mode 100644 index 0000000..525595f --- /dev/null +++ b/tests/manual/gds/gds.pro @@ -0,0 +1,8 @@ +TARGET = tst_gds + +QT += testlib opcua network +QT -= gui +CONFIG += testcase + +SOURCES += \ + tst_gds.cpp diff --git a/tests/manual/gds/tst_gds.cpp b/tests/manual/gds/tst_gds.cpp new file mode 100644 index 0000000..9e57238 --- /dev/null +++ b/tests/manual/gds/tst_gds.cpp @@ -0,0 +1,329 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt OPC UA module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPLv3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtOpcUa/QOpcUaProvider> +#include <QtOpcUa/QOpcUaGdsClient> +#include <QtOpcUa/QOpcUaApplicationIdentity> +#include <QtOpcUa/QOpcUaPkiConfiguration> +#include <QtOpcUa/QOpcUaAuthenticationInformation> +#include <QtOpcUa/QOpcUaEndpointDescription> +#include <QtOpcUa/QOpcUaApplicationRecordDataType> +#include <QtOpcUa/QOpcUaClient> +#include <QtOpcUa/QOpcUaX509DistinguishedName> + +#include <QtCore/QCoreApplication> +#include <QtCore/QScopedPointer> +#include <QHostInfo> + +#include <QtTest/QSignalSpy> +#include <QtTest/QtTest> + +#define defineDataMethod(name) void name()\ +{\ + QTest::addColumn<QString>("backend");\ + for (auto backend : m_backends) {\ + const QString rowName = QString("%1").arg(backend); \ + QTest::newRow(rowName.toLatin1().constData()) << backend ; \ + QVERIFY(!backend.isEmpty()); \ + }\ +} + +static QOpcUaPkiConfiguration getPkiConfig() +{ + QOpcUaPkiConfiguration pkiConfig; + const QString pkiDir = QCoreApplication::applicationDirPath() + "/pki"; + + pkiConfig.setClientCertificateFile(pkiDir + "/own/certs/application.der"); + pkiConfig.setPrivateKeyFile(pkiDir + "/own/private/application.pem"); + pkiConfig.setTrustListDirectory(pkiDir + "/trusted/certs"); + pkiConfig.setRevocationListDirectory(pkiDir + "/trusted/crl"); + pkiConfig.setIssuerListDirectory(pkiDir + "/issuers/certs"); + pkiConfig.setIssuerRevocationListDirectory(pkiDir + "/issuers/crl"); + return pkiConfig; +} + +static QOpcUaApplicationIdentity getAppIdentity() +{ + QOpcUaApplicationIdentity identity; + + const QString applicationUri = QStringLiteral("urn:%1:%2:%3") + .arg(QHostInfo::localHostName()) + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()); + const QString productUri = QStringLiteral("urn:%1:%2") + .arg(QCoreApplication::organizationName()) + .arg(QCoreApplication::applicationName()); + + identity.setProductUri(productUri); + identity.setApplicationUri(applicationUri); + identity.setApplicationName(QCoreApplication::applicationName()); + identity.setApplicationType(QOpcUaApplicationDescription::Client); + return identity; +} + +static bool removeSettingsFile() +{ + QSettings settings; + QFile file(settings.fileName()); + if (file.exists()) + return file.remove(); + return true; +} + +static void provideCredentials(QOpcUaAuthenticationInformation &authInfo) +{ + qDebug() << "Authentication credentials provided"; + authInfo.setUsernameAuthentication("root", "secret"); +} + +static void commonGdsClientSetup(QOpcUaGdsClient &gc, const QString &backend, const QOpcUaEndpointDescription endpoint) +{ + QObject::connect(&gc, &QOpcUaGdsClient::authenticationRequired, provideCredentials); + + gc.setBackend(backend); + gc.setEndpoint(endpoint); + gc.setApplicationIdentity(getAppIdentity()); + gc.setPkiConfiguration(getPkiConfig()); + + QOpcUaApplicationRecordDataType ar = gc.applicationRecord(); + ar.setApplicationNames(QVector<QOpcUaLocalizedText>{QOpcUaLocalizedText("en", gc.applicationIdentity().applicationName())}); + ar.setApplicationType(gc.applicationIdentity().applicationType()); + ar.setApplicationUri(gc.applicationIdentity().applicationUri()); + ar.setProductUri(gc.applicationIdentity().productUri()); + ar.setDiscoveryUrls(QVector<QString>{QLatin1String("opc.tcp://localhost")}); + gc.setApplicationRecord(ar); + + QOpcUaX509DistinguishedName dn; + dn.setEntry(QOpcUaX509DistinguishedName::Type::CountryName, QLatin1String("DE")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::LocalityName, QLatin1String("Berlin")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::StateOrProvinceName, QLatin1String("Berlin")); + dn.setEntry(QOpcUaX509DistinguishedName::Type::OrganizationName, QLatin1String("The Qt Company")); + gc.setCertificateSigningRequestPresets(dn, QLatin1String("foo.com")); +} + +// The tests have are depending on each other and have to be executed in order. + +class Tst_QOpcUaGds: public QObject +{ + Q_OBJECT + +public: + Tst_QOpcUaGds(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + + defineDataMethod(registerApplication_data) + void registerApplication(); + defineDataMethod(reuseApplicationId_data) + void reuseApplicationId(); + defineDataMethod(reuseRegisteredUri_data) + void reuseRegisteredUri(); + defineDataMethod(serverForgotRegistration_data) + void serverForgotRegistration(); +private: + QStringList m_backends; + QOpcUaEndpointDescription m_endpoint; +}; + +Tst_QOpcUaGds::Tst_QOpcUaGds() +{ + m_backends = QOpcUaProvider::availableBackends(); +} + +void Tst_QOpcUaGds::initTestCase() +{ + QOpcUaEndpointDescription endpoint; + QOpcUaProvider provider; + + QScopedPointer<QOpcUaClient> client(provider.createClient("open62541")); + QVERIFY(!client.isNull()); + QSignalSpy endpointSpy(client.data(), &QOpcUaClient::endpointsRequestFinished); + client->requestEndpoints(QUrl("opc.tcp://172.17.0.1:48060")); + endpointSpy.wait(2000); + QCOMPARE(endpointSpy.size(), 1); + + const QVector<QOpcUaEndpointDescription> desc = endpointSpy.at(0).at(0).value<QVector<QOpcUaEndpointDescription>>(); + QVERIFY(desc.size() > 0); + + for (const auto &i : qAsConst(desc)) { + if (i.securityPolicy().endsWith("#Basic256Sha256") + && i.securityMode() == QOpcUaEndpointDescription::MessageSecurityMode::SignAndEncrypt) + m_endpoint = i; + } + + QVERIFY(m_endpoint.securityPolicy().endsWith("#Basic256Sha256")); +} + +void Tst_QOpcUaGds::registerApplication() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + QVERIFY(removeSettingsFile()); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + QSignalSpy certificateStatusSpy(&gc, &QOpcUaGdsClient::certificateUpdateRequired); + QSignalSpy certificateUpdatedSpy(&gc, &QOpcUaGdsClient::certificateUpdated); + QSignalSpy trustListUpdatedSpy(&gc, &QOpcUaGdsClient::trustListUpdated); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); + + if (certificateStatusSpy.isEmpty()) + certificateStatusSpy.wait(); + + if (certificateUpdatedSpy.isEmpty()) + certificateUpdatedSpy.wait(); + + if (trustListUpdatedSpy.isEmpty()) + trustListUpdatedSpy.wait(); + + QVERIFY(!certificateStatusSpy.isEmpty()); + QVERIFY(!certificateUpdatedSpy.isEmpty()); + QVERIFY(!trustListUpdatedSpy.isEmpty()); +} + +void Tst_QOpcUaGds::reuseApplicationId() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Keep settings file in order to reuse the existing registration + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); +} + +void Tst_QOpcUaGds::reuseRegisteredUri() +{ + // This test assumes that a registratin at the server is still present. + // It will remove the information locally and has to reuse the + // registration from the server. + + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Remove the registration data locally; the server will keep it. + QSettings settings("Unknown Organization", "tst_gds"); + + QString applicationId = settings.value(QLatin1String("gds/applicationId")).toString(); + settings.remove(QLatin1String("gds/applicationId")); + settings.sync(); + + QVERIFY(removeSettingsFile()); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); + QVERIFY(gc.applicationId().size() > 10); + if (!applicationId.isEmpty()) + QVERIFY(gc.applicationId() == applicationId); +} + +void Tst_QOpcUaGds::serverForgotRegistration() +{ + QFETCH(QString, backend); + if (backend == "open62541") + QSKIP("Skipping open62541"); + + // Setting an invalid application ID simulates a previous registration which has been + // forgotten by the server + QSettings settings("Unknown Organization", "tst_gds"); + settings.setValue(QLatin1String("gds/applicationId"), "ns=42;i=1111111"); + settings.sync(); + + QOpcUaGdsClient gc; + commonGdsClientSetup(gc, backend, m_endpoint); + + QSignalSpy registeredSpy(&gc, &QOpcUaGdsClient::applicationRegistered); + QSignalSpy certificateGroupsSpy(&gc, &QOpcUaGdsClient::certificateGroupsReceived); + + gc.start(); + + QVERIFY(certificateGroupsSpy.wait()); + QVERIFY(!certificateGroupsSpy.at(0).at(0).value<QStringList>().isEmpty()); + QVERIFY(registeredSpy.wait()); +} + +void Tst_QOpcUaGds::cleanupTestCase() +{ +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + QTEST_SET_MAIN_SOURCE_PATH + + if (QOpcUaProvider::availableBackends().empty()) { + qDebug("No OPCUA backends found, skipping tests."); + return EXIT_SUCCESS; + } + + Tst_QOpcUaGds tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "tst_gds.moc" diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 3ee3e86..6007738 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -1,3 +1,6 @@ TEMPLATE = subdirs SUBDIRS += eventsubscription + +QT_FOR_CONFIG += opcua-private core-private +qtConfig(ssl):!darwin:!winrt: SUBDIRS += gds diff --git a/tests/tests.pro b/tests/tests.pro index e7b0667..5719b55 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -1,6 +1,5 @@ TEMPLATE = subdirs -SUBDIRS += auto \ - manual +SUBDIRS += auto QT_FOR_CONFIG += opcua-private |