From c92031cf5f67f3642846da14caec5a53a4bb51b7 Mon Sep 17 00:00:00 2001 From: Karsten Heimrich Date: Thu, 7 Mar 2019 11:19:33 +0100 Subject: Rename class to match the real use-case more closely The secure configuration can only be used with with KNXnet/IP tunneling or device management, so make the name more explicit. Change-Id: Iba3c63bfc00b081dd18b2ee7f3e3d516fa49688d Reviewed-by: Maurice Kalinowski --- src/knx/netip/netip.pri | 9 +- src/knx/netip/qknxnetipendpointconnection.cpp | 6 +- src/knx/netip/qknxnetipendpointconnection.h | 6 +- src/knx/netip/qknxnetipendpointconnection_p.h | 4 +- src/knx/netip/qknxnetipsecureconfiguration.cpp | 446 +++++++++++++++++++++++++ src/knx/netip/qknxnetipsecureconfiguration.h | 103 ++++++ src/knx/netip/qknxnetipsecureconfiguration_p.h | 67 ++++ src/knx/ssl/qknxsecureconfiguration.cpp | 446 ------------------------- src/knx/ssl/qknxsecureconfiguration.h | 103 ------ src/knx/ssl/qknxsecureconfiguration_p.h | 67 ---- src/knx/ssl/ssl.pri | 3 - 11 files changed, 630 insertions(+), 630 deletions(-) create mode 100644 src/knx/netip/qknxnetipsecureconfiguration.cpp create mode 100644 src/knx/netip/qknxnetipsecureconfiguration.h create mode 100644 src/knx/netip/qknxnetipsecureconfiguration_p.h delete mode 100644 src/knx/ssl/qknxsecureconfiguration.cpp delete mode 100644 src/knx/ssl/qknxsecureconfiguration.h delete mode 100644 src/knx/ssl/qknxsecureconfiguration_p.h diff --git a/src/knx/netip/netip.pri b/src/knx/netip/netip.pri index 5652563..88e2d58 100644 --- a/src/knx/netip/netip.pri +++ b/src/knx/netip/netip.pri @@ -53,7 +53,8 @@ PUBLIC_HEADERS += $$PWD/qknxnetip.h \ $$PWD/qknxnetipsessionstatus.h \ $$PWD/qknxnetiptimernotify.h \ $$PWD/qknxnetipsecurewrapper.h \ - $$PWD/qknxnetiprouter.h + $$PWD/qknxnetiprouter.h \ + $$PWD/qknxnetipsecureconfiguration.h PRIVATE_HEADERS += \ $$PWD/qknxbuilderdata_p.h \ @@ -61,7 +62,8 @@ PRIVATE_HEADERS += \ $$PWD/qknxnetipserverdescriptionagent_p.h \ $$PWD/qknxnetipserverdiscoveryagent_p.h \ $$PWD/qknxnetipserverinfo_p.h \ - $$PWD/qknxnetiptestrouter_p.h + $$PWD/qknxnetiptestrouter_p.h \ + $$PWD/qknxnetipsecureconfiguration_p.h SOURCES += $$PWD/qknxnetip.cpp \ $$PWD/qknxnetipconfigdib.cpp \ @@ -117,4 +119,5 @@ SOURCES += $$PWD/qknxnetip.cpp \ $$PWD/qknxnetiptimernotify.cpp \ $$PWD/qknxnetipsecurewrapper.cpp \ $$PWD/qknxnetiprouter.cpp \ - $$PWD/qknxnetiprouter_p.cpp + $$PWD/qknxnetiprouter_p.cpp \ + $$PWD/qknxnetipsecureconfiguration.cpp diff --git a/src/knx/netip/qknxnetipendpointconnection.cpp b/src/knx/netip/qknxnetipendpointconnection.cpp index c1769bf..1f8d616 100644 --- a/src/knx/netip/qknxnetipendpointconnection.cpp +++ b/src/knx/netip/qknxnetipendpointconnection.cpp @@ -53,7 +53,7 @@ #include "qtcpsocket.h" #include "qudpsocket.h" -#include "private/qknxsecureconfiguration_p.h" +#include "private/qknxnetipsecureconfiguration_p.h" QT_BEGIN_NAMESPACE /*! @@ -1306,7 +1306,7 @@ void QKnxNetIpEndpointConnection::setSerialNumber(const QKnxByteArray &serialNum Returns the secure configuration used to establish the secure session. */ -QKnxSecureConfiguration QKnxNetIpEndpointConnection::secureConfiguration() const +QKnxNetIpSecureConfiguration QKnxNetIpEndpointConnection::secureConfiguration() const { Q_D(const QKnxNetIpEndpointConnection); return d->m_secureConfig; @@ -1319,7 +1319,7 @@ QKnxSecureConfiguration QKnxNetIpEndpointConnection::secureConfiguration() const to \a {config}. The configuration cannot be changed while the secure connection is established. */ -void QKnxNetIpEndpointConnection::setSecureConfiguration(const QKnxSecureConfiguration &config) +void QKnxNetIpEndpointConnection::setSecureConfiguration(const QKnxNetIpSecureConfiguration &config) { Q_D(QKnxNetIpEndpointConnection); if (d->m_state == QKnxNetIpEndpointConnection::State::Disconnected) diff --git a/src/knx/netip/qknxnetipendpointconnection.h b/src/knx/netip/qknxnetipendpointconnection.h index d401b82..4c94b78 100644 --- a/src/knx/netip/qknxnetipendpointconnection.h +++ b/src/knx/netip/qknxnetipendpointconnection.h @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include @@ -121,8 +121,8 @@ public: QKnxByteArray serialNumber() const; void setSerialNumber(const QKnxByteArray &serialNumber); - QKnxSecureConfiguration secureConfiguration() const; - void setSecureConfiguration(const QKnxSecureConfiguration &config); + QKnxNetIpSecureConfiguration secureConfiguration() const; + void setSecureConfiguration(const QKnxNetIpSecureConfiguration &config); void connectToHostEncrypted(const QKnxNetIpHpai &controlEndpoint); void connectToHostEncrypted(const QHostAddress &address, quint16 port); diff --git a/src/knx/netip/qknxnetipendpointconnection_p.h b/src/knx/netip/qknxnetipendpointconnection_p.h index 9d0e0b7..d7b3363 100644 --- a/src/knx/netip/qknxnetipendpointconnection_p.h +++ b/src/knx/netip/qknxnetipendpointconnection_p.h @@ -48,7 +48,7 @@ #include #include #include -#include +#include #include @@ -225,7 +225,7 @@ private: QTimer *m_secureTimer { nullptr }; QKnxSecureKey m_serverPublicKey; - QKnxSecureConfiguration m_secureConfig; + QKnxNetIpSecureConfiguration m_secureConfig; // TODO: We need some kind of device configuration class as well. QKnxByteArray m_serialNumber { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/src/knx/netip/qknxnetipsecureconfiguration.cpp b/src/knx/netip/qknxnetipsecureconfiguration.cpp new file mode 100644 index 0000000..eacfd4d --- /dev/null +++ b/src/knx/netip/qknxnetipsecureconfiguration.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtKnx module. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qknxcryptographicengine.h" +#include "qknxnetipsecureconfiguration.h" + +#include + +#include "private/qknxkeyring_p.h" +#include "private/qknxnetipsecureconfiguration_p.h" + +QT_BEGIN_NAMESPACE + +namespace QKnxPrivate +{ + static QKnxNetIpSecureConfiguration fromInterface(const QKnx::Ets::Keyring::QKnxInterface &iface, + const QKnxByteArray &pwHash, const QKnxByteArray &createdHash) + { + QKnxNetIpSecureConfiguration s; + s.setUserId(QKnxNetIp::SecureUserId(iface.UserID)); + s.setPrivateKey(QKnxSecureKey::generatePrivateKey()); + s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, + createdHash, iface.Password).toByteArray()); + s.setIndividualAddress({ QKnxAddress::Type::Individual, iface.IndividualAddress }); + s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, + createdHash, iface.Authentication).toByteArray()); + return s; + } + + static QKnxNetIpSecureConfiguration fromDevice(const QKnx::Ets::Keyring::QKnxDevice &device, + const QKnxByteArray &pwHash, const QKnxByteArray &createdHash) + { + QKnxNetIpSecureConfiguration s; + s.setUserId(QKnxNetIp::SecureUserId::Management); + s.setPrivateKey(QKnxSecureKey::generatePrivateKey()); + s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, + createdHash, device.ManagementPassword).toByteArray()); + s.setIndividualAddress({ QKnxAddress::Type::Individual, device.IndividualAddress }); + s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, + createdHash, device.Authentication).toByteArray()); + return s; + } + + static QVector fromKeyring(QKnxNetIpSecureConfiguration::Type type, + const QKnxAddress &ia, const QString &filePath, const QByteArray &password, bool validate) + { + QFile file; + file.setFileName(filePath); + if (!file.open(QIODevice::ReadOnly)) + return {}; + + QXmlStreamReader reader(&file); + QKnx::Ets::Keyring::QKnxKeyring keyring; + + const auto pwHash = QKnxCryptographicEngine::keyringPasswordHash(password); + if (validate) { + if (!keyring.validate(&reader, pwHash)) + return {}; + file.seek(0); + reader.setDevice(&file); + } + + if (!keyring.parseElement(&reader, true)) + return {}; + const auto createdHash = QKnxCryptographicEngine::hashSha256(keyring.Created.toUtf8()); + + auto iaString = ia.toString(); + QVector results; + + if (type == QKnxNetIpSecureConfiguration::Type::Tunneling) { + if (keyring.Interface.isEmpty()) + return {}; + + if (!iaString.isEmpty()) { // only a single interface is requested + for (const auto iface : qAsConst(keyring.Interface)) { + if (iaString != iface.IndividualAddress) + continue; + return { QKnxPrivate::fromInterface(iface, pwHash, createdHash) }; + } + } else { + for (const auto iface : qAsConst(keyring.Interface)) + results.append(QKnxPrivate::fromInterface(iface, pwHash, createdHash)); + } + } + + if (type == QKnxNetIpSecureConfiguration::Type::DeviceManagement) { + if (keyring.Devices.isEmpty()) + return {}; + + const auto devices = keyring.Devices.value(0).Device; + if (!iaString.isEmpty()) { // only a single device is requested + for (const auto device : devices) { + if (iaString != device.IndividualAddress) + continue; + return { QKnxPrivate::fromDevice(device, pwHash, createdHash) }; + } + } else { + for (const auto device : devices) + results.append(QKnxPrivate::fromDevice(device, pwHash, createdHash)); + } + } + + return results; + } +} + +/*! + \since 5.13 + \inmodule QtKnx + + \class QKnxNetIpSecureConfiguration + \ingroup qtknx-general-classes + + \brief The QKnxNetIpSecureConfiguration class holds configuration + options used for the secure session authentication process. + + It holds information such as secure key, user ID and password, device + authentication code, and so on. + + This class is part of the Qt KNX module and 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 QKnxNetIpSecureConfiguration::Type + + This enum holds the type of secure configuration that can be constructed + from an ETS exported keyring (*.knxkeys) file. + + \value Tunneling + KNXnet/IP secure tunneling configuration. + \value DeviceManagement + KNXnet/IP secure device management configuration. +*/ + +/*! + Constructs a new, empty, invalid secure configuration. + + \sa isNull(), isValid() +*/ +QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration() + : d(new QKnxNetIpSecureConfigurationPrivate) +{} + +/*! + Releases any resources held by the secure configuration. +*/ +QKnxNetIpSecureConfiguration::~QKnxNetIpSecureConfiguration() = default; + +/*! + Constructs a vector of secure configurations for the given type + \a type from an ETS exported \a keyring (*.knxkeys) file that was + encrypted with the given password \a password. Set the \a validate + argument to \c true to verify that all data in the keyring file is + trustworthy, \c false to omit the check. + + \note If an error occurred, no or invalid information for \a type + was found in the keyring file, the returned vector can be empty. +*/ +QVector QKnxNetIpSecureConfiguration::fromKeyring(Type type, + const QString &keyring, const QByteArray &password, bool validate) +{ + return QKnxPrivate::fromKeyring(type, {}, keyring, password, validate); +} + +/*! + Constructs a secure configurations for the given type \a type and the + given individual address \a ia from an ETS exported \a keyring (*.knxkeys) + file that was encrypted with the given password \a password. + Set the \a validate argument to \c true to verify that all data in the + keyring file is trustworthy, \c false to omit the check. + + \note If an error occurred, no or invalid information for \a type or \a ia + was found in the keyring file; + the function returns a \l {default-constructed value} which can be invalid. +*/ +QKnxNetIpSecureConfiguration QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration::Type type, + const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate) +{ + return QKnxPrivate::fromKeyring(type, ia, keyring, password, validate).value(0, {}); +} + +/*! + Returns \c true if this is a default constructed secure configuration; + otherwise returns \c false. A secure configuration is considered \c null + if it contains no initialized values. +*/ +bool QKnxNetIpSecureConfiguration::isNull() const +{ + return d->privateKey.isNull() && d->userId == QKnxNetIp::SecureUserId::Reserved + && d->userPassword.isNull() && d->deviceAuthenticationCode.isNull(); +} + +/*! + Returns \c true if the secure configuration contains initialized values and + is in itself valid, otherwise returns \c false. + + A valid secure configuration consists of at least a valid user ID, + a valid \l {QKnxSecureKey} {secure key}, and sensible device authentication + code. +*/ +bool QKnxNetIpSecureConfiguration::isValid() const +{ + if (isNull()) + return false; + + return d->privateKey.type() == QKnxSecureKey::Type::Private && d->privateKey.isValid() + && QKnxNetIp::isSecureUserId(d->userId) && (!d->deviceAuthenticationCode.isEmpty()); +} + +/*! + Returns the public \l {QKnxSecureKey} {secure key} used to establish the + secure session. The public key is derived from the given private key. +*/ +QKnxSecureKey QKnxNetIpSecureConfiguration::publicKey() const +{ + return d->publicKey; +} + +/*! + Returns the private \l {QKnxSecureKey} {secure key} used to establish the + secure session. +*/ +QKnxSecureKey QKnxNetIpSecureConfiguration::privateKey() const +{ + return d->privateKey; +} + +/*! + Set the \l {QKnxSecureKey} {secure key} used to establish the secure + connection to \a key and returns \c true on success; \c false otherwise. +*/ +bool QKnxNetIpSecureConfiguration::setPrivateKey(const QKnxSecureKey &key) +{ + auto valid = key.isValid() && key.type() == QKnxSecureKey::Type::Private; + if (valid) { + d->privateKey = key; + d->publicKey = QKnxSecureKey::publicKeyFromPrivate(key); + } + return valid; +} + +/*! + Returns the user ID used in the KNXnet/IP session authentication frame. +*/ +QKnxNetIp::SecureUserId QKnxNetIpSecureConfiguration::userId() const +{ + return d->userId; +} + +/*! + Sets the user ID used in the KNXnet/IP session authentication frame + to \a userId and returns \c true on success; \c false otherwise. + + \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or + equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered + invalid according to the KNX application note AN159. +*/ +bool QKnxNetIpSecureConfiguration::setUserId(QKnxNetIp::SecureUserId userId) +{ + auto valid = QKnxNetIp::isSecureUserId(userId); + if (valid) + d->userId = userId; + return valid; +} + +/*! + Returns the user password used to authenticate the user while establishing + the secure session as an array of bytes. +*/ +QByteArray QKnxNetIpSecureConfiguration::userPassword() const +{ + return d->userPassword; +} + +/*! + Sets the user password to authenticate the user while establishing the + secure session to \a userPassword. Returns \c true on success; \c false + otherwise. +*/ +void QKnxNetIpSecureConfiguration::setUserPassword(const QByteArray &userPassword) +{ + d->userPassword = userPassword; +} + +/*! + Returns the requested individual address for the secure session. +*/ +QKnxAddress QKnxNetIpSecureConfiguration::individualAddress() const +{ + return d->ia; +} + +/*! + Sets the requested individual address of the secure session to \a address. + Returns \c true on success; \c false otherwise. + + \note To request any of the freely available addresses for the secure + session, or to reset the requested one, pass an invalid \a address to + the function. +*/ +bool QKnxNetIpSecureConfiguration::setIndividualAddress(const QKnxAddress &address) +{ + if ((address.type() == QKnxAddress::Type::Individual) || (!address.isValid())) + d->ia = address; + return d->ia == address; +} + +/*! + Returns the device authentication code to establish the secure session + as an array of bytes. +*/ +QByteArray QKnxNetIpSecureConfiguration::deviceAuthenticationCode() const +{ + return d->deviceAuthenticationCode; +} + +/*! + Sets the device authentication code used to establish the secure session + to \a authenticationCode. Returns \c true on success; \c false otherwise. + + \note The device authentication code cannot be empty. +*/ +bool QKnxNetIpSecureConfiguration::setDeviceAuthenticationCode(const QByteArray &authenticationCode) +{ + auto valid = !authenticationCode.isEmpty(); + if (valid) + d->deviceAuthenticationCode = authenticationCode; + return valid; +} + +/*! + Returns \c true if the keep alive flag is set; \c false otherwise. By + default this is set to \c false. +*/ +bool QKnxNetIpSecureConfiguration::isSecureSessionKeepAliveSet() const +{ + return d->keepAlive; +} + +/*! + Determines whether the connection should be kept alive. Set \a keepAlive to + \c true to keep a secure session alive even if there is no traffic for more + than \l {QKnx::NetIp::SecureSessionTimeout} {60 seconds}. +*/ +void QKnxNetIpSecureConfiguration::setKeepSecureSessionAlive(bool keepAlive) +{ + d->keepAlive = keepAlive; +} + +/*! + Constructs a copy of \a other. +*/ +QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration(const QKnxNetIpSecureConfiguration &other) + : d(other.d) +{} + +/*! + Assigns the specified \a other to this secure configuration. +*/ +QKnxNetIpSecureConfiguration &QKnxNetIpSecureConfiguration::operator=(const QKnxNetIpSecureConfiguration &other) +{ + d = other.d; + return *this; +} + +/*! + Move-constructs a secure configuration, making it point to the same secure + configuration that \a other was pointing to. +*/ +QKnxNetIpSecureConfiguration::QKnxNetIpSecureConfiguration(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW + : d(other.d) +{ + other.d = nullptr; +} + +/*! + Move-assigns \a other to this secure configuration. +*/ +QKnxNetIpSecureConfiguration & + QKnxNetIpSecureConfiguration::operator=(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW +{ + swap(other); + return *this; +} + +/*! + Swaps \a other with this secure configuration. This operation is very fast + and never fails. +*/ +void QKnxNetIpSecureConfiguration::swap(QKnxNetIpSecureConfiguration &other) Q_DECL_NOTHROW +{ + d.swap(other.d); +} + +/*! + Returns \c true if this secure configuration and the given \a other are + equal; otherwise returns \c false. +*/ +bool QKnxNetIpSecureConfiguration::operator==(const QKnxNetIpSecureConfiguration &other) const +{ + return d == other.d || (d->privateKey == other.d->privateKey + && d->userId == other.d->userId + && d->userPassword == other.d->userPassword + && d->ia == other.d->ia + && d->deviceAuthenticationCode == other.d->deviceAuthenticationCode + && d->keepAlive == other.d->keepAlive); +} + +/*! + Returns \c true if this secure configuration and the given \a other are not + equal; otherwise returns \c false. +*/ +bool QKnxNetIpSecureConfiguration::operator!=(const QKnxNetIpSecureConfiguration &other) const +{ + return !operator==(other); +} + +QT_END_NAMESPACE diff --git a/src/knx/netip/qknxnetipsecureconfiguration.h b/src/knx/netip/qknxnetipsecureconfiguration.h new file mode 100644 index 0000000..a7125d3 --- /dev/null +++ b/src/knx/netip/qknxnetipsecureconfiguration.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtKnx module. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QKNXNETIPSECURECONFIGURATION_H +#define QKNXNETIPSECURECONFIGURATION_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QKnxNetIpSecureConfigurationPrivate; +class Q_KNX_EXPORT QKnxNetIpSecureConfiguration +{ +public: + enum class Type : quint8 + { + Tunneling = 0x00, + DeviceManagement = 001 + }; + + QKnxNetIpSecureConfiguration(); + ~QKnxNetIpSecureConfiguration(); + + static QVector fromKeyring(QKnxNetIpSecureConfiguration::Type type, + const QString &keyring, const QByteArray &password, bool validate); + + static QKnxNetIpSecureConfiguration fromKeyring(QKnxNetIpSecureConfiguration::Type type, + const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate); + + bool isNull() const; + bool isValid() const; + + QKnxSecureKey publicKey() const; + + QKnxSecureKey privateKey() const; + bool setPrivateKey(const QKnxSecureKey &key); + + QKnxNetIp::SecureUserId userId() const; + bool setUserId(QKnxNetIp::SecureUserId userId); + + QByteArray userPassword() const; + void setUserPassword(const QByteArray &userPassword); + + QKnxAddress individualAddress() const; + bool setIndividualAddress(const QKnxAddress &address); + + QByteArray deviceAuthenticationCode() const; + bool setDeviceAuthenticationCode(const QByteArray &authenticationCode); + + bool isSecureSessionKeepAliveSet() const; + void setKeepSecureSessionAlive(bool keepAlive); + + QKnxNetIpSecureConfiguration(const QKnxNetIpSecureConfiguration &other); + QKnxNetIpSecureConfiguration &operator=(const QKnxNetIpSecureConfiguration &other); + + QKnxNetIpSecureConfiguration(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW; + QKnxNetIpSecureConfiguration &operator=(QKnxNetIpSecureConfiguration &&other) Q_DECL_NOTHROW; + + void swap(QKnxNetIpSecureConfiguration &other) Q_DECL_NOTHROW; + + bool operator==(const QKnxNetIpSecureConfiguration &other) const; + bool operator!=(const QKnxNetIpSecureConfiguration &other) const; + +private: + friend class QKnxNetIpEndpointConnection; + friend class QKnxNetIpEndpointConnectionPrivate; + QSharedDataPointer d; +}; +Q_DECLARE_SHARED(QKnxNetIpSecureConfiguration) + +QT_END_NAMESPACE + +#endif diff --git a/src/knx/netip/qknxnetipsecureconfiguration_p.h b/src/knx/netip/qknxnetipsecureconfiguration_p.h new file mode 100644 index 0000000..c56dd1e --- /dev/null +++ b/src/knx/netip/qknxnetipsecureconfiguration_p.h @@ -0,0 +1,67 @@ +/****************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtKnx module. +** +** $QT_BEGIN_LICENSE:GPL$ +** 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 General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 or (at your option) 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.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-3.0.html. +** +** $QT_END_LICENSE$ +** +******************************************************************************/ + +#ifndef QKNXNETIPSECURECONFIGURATION_P_H +#define QKNXNETIPSECURECONFIGURATION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt KNX API. It exists for the convenience +// of the Qt KNX implementation. This header file may change from version +// to version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QKnxNetIpSecureConfigurationPrivate : public QSharedData +{ +public: + QKnxNetIpSecureConfigurationPrivate() = default; + ~QKnxNetIpSecureConfigurationPrivate() = default; + + QKnxSecureKey privateKey; + QKnxSecureKey publicKey; + QKnxNetIp::SecureUserId userId { QKnxNetIp::SecureUserId::Reserved }; + QByteArray userPassword; + QKnxAddress ia; + QByteArray deviceAuthenticationCode; + bool keepAlive { false }; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/knx/ssl/qknxsecureconfiguration.cpp b/src/knx/ssl/qknxsecureconfiguration.cpp deleted file mode 100644 index 5516bdb..0000000 --- a/src/knx/ssl/qknxsecureconfiguration.cpp +++ /dev/null @@ -1,446 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtKnx module. -** -** $QT_BEGIN_LICENSE:GPL$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 or (at your option) 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.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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qknxcryptographicengine.h" -#include "qknxsecureconfiguration.h" - -#include - -#include "private/qknxkeyring_p.h" -#include "private/qknxsecureconfiguration_p.h" - -QT_BEGIN_NAMESPACE - -namespace QKnxPrivate -{ - static QKnxSecureConfiguration fromInterface(const QKnx::Ets::Keyring::QKnxInterface &iface, - const QKnxByteArray &pwHash, const QKnxByteArray &createdHash) - { - QKnxSecureConfiguration s; - s.setUserId(QKnxNetIp::SecureUserId(iface.UserID)); - s.setPrivateKey(QKnxSecureKey::generatePrivateKey()); - s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, - createdHash, iface.Password).toByteArray()); - s.setIndividualAddress({ QKnxAddress::Type::Individual, iface.IndividualAddress }); - s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, - createdHash, iface.Authentication).toByteArray()); - return s; - } - - static QKnxSecureConfiguration fromDevice(const QKnx::Ets::Keyring::QKnxDevice &device, - const QKnxByteArray &pwHash, const QKnxByteArray &createdHash) - { - QKnxSecureConfiguration s; - s.setUserId(QKnxNetIp::SecureUserId::Management); - s.setPrivateKey(QKnxSecureKey::generatePrivateKey()); - s.setUserPassword(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, - createdHash, device.ManagementPassword).toByteArray()); - s.setIndividualAddress({ QKnxAddress::Type::Individual, device.IndividualAddress }); - s.setDeviceAuthenticationCode(QKnxCryptographicEngine::decodeAndDecryptPassword(pwHash, - createdHash, device.Authentication).toByteArray()); - return s; - } - - static QVector fromKeyring(QKnxSecureConfiguration::Type type, - const QKnxAddress &ia, const QString &filePath, const QByteArray &password, bool validate) - { - QFile file; - file.setFileName(filePath); - if (!file.open(QIODevice::ReadOnly)) - return {}; - - QXmlStreamReader reader(&file); - QKnx::Ets::Keyring::QKnxKeyring keyring; - - const auto pwHash = QKnxCryptographicEngine::keyringPasswordHash(password); - if (validate) { - if (!keyring.validate(&reader, pwHash)) - return {}; - file.seek(0); - reader.setDevice(&file); - } - - if (!keyring.parseElement(&reader, true)) - return {}; - const auto createdHash = QKnxCryptographicEngine::hashSha256(keyring.Created.toUtf8()); - - auto iaString = ia.toString(); - QVector results; - - if (type == QKnxSecureConfiguration::Type::Tunneling) { - if (keyring.Interface.isEmpty()) - return {}; - - if (!iaString.isEmpty()) { // only a single interface is requested - for (const auto iface : qAsConst(keyring.Interface)) { - if (iaString != iface.IndividualAddress) - continue; - return { QKnxPrivate::fromInterface(iface, pwHash, createdHash) }; - } - } else { - for (const auto iface : qAsConst(keyring.Interface)) - results.append(QKnxPrivate::fromInterface(iface, pwHash, createdHash)); - } - } - - if (type == QKnxSecureConfiguration::Type::DeviceManagement) { - if (keyring.Devices.isEmpty()) - return {}; - - const auto devices = keyring.Devices.value(0).Device; - if (!iaString.isEmpty()) { // only a single device is requested - for (const auto device : devices) { - if (iaString != device.IndividualAddress) - continue; - return { QKnxPrivate::fromDevice(device, pwHash, createdHash) }; - } - } else { - for (const auto device : devices) - results.append(QKnxPrivate::fromDevice(device, pwHash, createdHash)); - } - } - - return results; - } -} - -/*! - \since 5.13 - \inmodule QtKnx - - \class QKnxSecureConfiguration - \ingroup qtknx-general-classes - - \brief The QKnxSecureConfiguration class holds configuration - options used for the secure session authentication process. - - It holds information such as secure key, user ID and password, device - authentication code, and so on. - - This class is part of the Qt KNX module and 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 QKnxSecureConfiguration::Type - - This enum holds the type of secure configuration that can be constructed - from an ETS exported keyring (*.knxkeys) file. - - \value Tunneling - KNXnet/IP secure tunneling configuration. - \value DeviceManagement - KNXnet/IP secure device management configuration. -*/ - -/*! - Constructs a new, empty, invalid secure configuration. - - \sa isNull(), isValid() -*/ -QKnxSecureConfiguration::QKnxSecureConfiguration() - : d(new QKnxSecureConfigurationPrivate) -{} - -/*! - Releases any resources held by the secure configuration. -*/ -QKnxSecureConfiguration::~QKnxSecureConfiguration() = default; - -/*! - Constructs a vector of secure configurations for the given type - \a type from an ETS exported \a keyring (*.knxkeys) file that was - encrypted with the given password \a password. Set the \a validate - argument to \c true to verify that all data in the keyring file is - trustworthy, \c false to omit the check. - - \note If an error occurred, no or invalid information for \a type - was found in the keyring file, the returned vector can be empty. -*/ -QVector QKnxSecureConfiguration::fromKeyring(Type type, - const QString &keyring, const QByteArray &password, bool validate) -{ - return QKnxPrivate::fromKeyring(type, {}, keyring, password, validate); -} - -/*! - Constructs a secure configurations for the given type \a type and the - given individual address \a ia from an ETS exported \a keyring (*.knxkeys) - file that was encrypted with the given password \a password. - Set the \a validate argument to \c true to verify that all data in the - keyring file is trustworthy, \c false to omit the check. - - \note If an error occurred, no or invalid information for \a type or \a ia - was found in the keyring file; - the function returns a \l {default-constructed value} which can be invalid. -*/ -QKnxSecureConfiguration QKnxSecureConfiguration::fromKeyring(QKnxSecureConfiguration::Type type, - const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate) -{ - return QKnxPrivate::fromKeyring(type, ia, keyring, password, validate).value(0, {}); -} - -/*! - Returns \c true if this is a default constructed secure configuration; - otherwise returns \c false. A secure configuration is considered \c null - if it contains no initialized values. -*/ -bool QKnxSecureConfiguration::isNull() const -{ - return d->privateKey.isNull() && d->userId == QKnxNetIp::SecureUserId::Reserved - && d->userPassword.isNull() && d->deviceAuthenticationCode.isNull(); -} - -/*! - Returns \c true if the secure configuration contains initialized values and - is in itself valid, otherwise returns \c false. - - A valid secure configuration consists of at least a valid user ID, - a valid \l {QKnxSecureKey} {secure key}, and sensible device authentication - code. -*/ -bool QKnxSecureConfiguration::isValid() const -{ - if (isNull()) - return false; - - return d->privateKey.type() == QKnxSecureKey::Type::Private && d->privateKey.isValid() - && QKnxNetIp::isSecureUserId(d->userId) && (!d->deviceAuthenticationCode.isEmpty()); -} - -/*! - Returns the public \l {QKnxSecureKey} {secure key} used to establish the - secure session. The public key is derived from the given private key. -*/ -QKnxSecureKey QKnxSecureConfiguration::publicKey() const -{ - return d->publicKey; -} - -/*! - Returns the private \l {QKnxSecureKey} {secure key} used to establish the - secure session. -*/ -QKnxSecureKey QKnxSecureConfiguration::privateKey() const -{ - return d->privateKey; -} - -/*! - Set the \l {QKnxSecureKey} {secure key} used to establish the secure - connection to \a key and returns \c true on success; \c false otherwise. -*/ -bool QKnxSecureConfiguration::setPrivateKey(const QKnxSecureKey &key) -{ - auto valid = key.isValid() && key.type() == QKnxSecureKey::Type::Private; - if (valid) { - d->privateKey = key; - d->publicKey = QKnxSecureKey::publicKeyFromPrivate(key); - } - return valid; -} - -/*! - Returns the user ID used in the KNXnet/IP session authentication frame. -*/ -QKnxNetIp::SecureUserId QKnxSecureConfiguration::userId() const -{ - return d->userId; -} - -/*! - Sets the user ID used in the KNXnet/IP session authentication frame - to \a userId and returns \c true on success; \c false otherwise. - - \note A userId() with the value \c QKnxNetIp::SecureUserId::Reserved or - equal to or more than \c QKnxNetIp::SecureUserId::Invalid is considered - invalid according to the KNX application note AN159. -*/ -bool QKnxSecureConfiguration::setUserId(QKnxNetIp::SecureUserId userId) -{ - auto valid = QKnxNetIp::isSecureUserId(userId); - if (valid) - d->userId = userId; - return valid; -} - -/*! - Returns the user password used to authenticate the user while establishing - the secure session as an array of bytes. -*/ -QByteArray QKnxSecureConfiguration::userPassword() const -{ - return d->userPassword; -} - -/*! - Sets the user password to authenticate the user while establishing the - secure session to \a userPassword. Returns \c true on success; \c false - otherwise. -*/ -void QKnxSecureConfiguration::setUserPassword(const QByteArray &userPassword) -{ - d->userPassword = userPassword; -} - -/*! - Returns the requested individual address for the secure session. -*/ -QKnxAddress QKnxSecureConfiguration::individualAddress() const -{ - return d->ia; -} - -/*! - Sets the requested individual address of the secure session to \a address. - Returns \c true on success; \c false otherwise. - - \note To request any of the freely available addresses for the secure - session, or to reset the requested one, pass an invalid \a address to - the function. -*/ -bool QKnxSecureConfiguration::setIndividualAddress(const QKnxAddress &address) -{ - if ((address.type() == QKnxAddress::Type::Individual) || (!address.isValid())) - d->ia = address; - return d->ia == address; -} - -/*! - Returns the device authentication code to establish the secure session - as an array of bytes. -*/ -QByteArray QKnxSecureConfiguration::deviceAuthenticationCode() const -{ - return d->deviceAuthenticationCode; -} - -/*! - Sets the device authentication code used to establish the secure session - to \a authenticationCode. Returns \c true on success; \c false otherwise. - - \note The device authentication code cannot be empty. -*/ -bool QKnxSecureConfiguration::setDeviceAuthenticationCode(const QByteArray &authenticationCode) -{ - auto valid = !authenticationCode.isEmpty(); - if (valid) - d->deviceAuthenticationCode = authenticationCode; - return valid; -} - -/*! - Returns \c true if the keep alive flag is set; \c false otherwise. By - default this is set to \c false. -*/ -bool QKnxSecureConfiguration::isSecureSessionKeepAliveSet() const -{ - return d->keepAlive; -} - -/*! - Determines whether the connection should be kept alive. Set \a keepAlive to - \c true to keep a secure session alive even if there is no traffic for more - than \l {QKnx::NetIp::SecureSessionTimeout} {60 seconds}. -*/ -void QKnxSecureConfiguration::setKeepSecureSessionAlive(bool keepAlive) -{ - d->keepAlive = keepAlive; -} - -/*! - Constructs a copy of \a other. -*/ -QKnxSecureConfiguration::QKnxSecureConfiguration(const QKnxSecureConfiguration &other) - : d(other.d) -{} - -/*! - Assigns the specified \a other to this secure configuration. -*/ -QKnxSecureConfiguration &QKnxSecureConfiguration::operator=(const QKnxSecureConfiguration &other) -{ - d = other.d; - return *this; -} - -/*! - Move-constructs a secure configuration, making it point to the same secure - configuration that \a other was pointing to. -*/ -QKnxSecureConfiguration::QKnxSecureConfiguration(QKnxSecureConfiguration &&other) Q_DECL_NOTHROW - : d(other.d) -{ - other.d = nullptr; -} - -/*! - Move-assigns \a other to this secure configuration. -*/ -QKnxSecureConfiguration & - QKnxSecureConfiguration::operator=(QKnxSecureConfiguration &&other) Q_DECL_NOTHROW -{ - swap(other); - return *this; -} - -/*! - Swaps \a other with this secure configuration. This operation is very fast - and never fails. -*/ -void QKnxSecureConfiguration::swap(QKnxSecureConfiguration &other) Q_DECL_NOTHROW -{ - d.swap(other.d); -} - -/*! - Returns \c true if this secure configuration and the given \a other are - equal; otherwise returns \c false. -*/ -bool QKnxSecureConfiguration::operator==(const QKnxSecureConfiguration &other) const -{ - return d == other.d || (d->privateKey == other.d->privateKey - && d->userId == other.d->userId - && d->userPassword == other.d->userPassword - && d->ia == other.d->ia - && d->deviceAuthenticationCode == other.d->deviceAuthenticationCode - && d->keepAlive == other.d->keepAlive); -} - -/*! - Returns \c true if this secure configuration and the given \a other are not - equal; otherwise returns \c false. -*/ -bool QKnxSecureConfiguration::operator!=(const QKnxSecureConfiguration &other) const -{ - return !operator==(other); -} - -QT_END_NAMESPACE diff --git a/src/knx/ssl/qknxsecureconfiguration.h b/src/knx/ssl/qknxsecureconfiguration.h deleted file mode 100644 index 6dd0e47..0000000 --- a/src/knx/ssl/qknxsecureconfiguration.h +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtKnx module. -** -** $QT_BEGIN_LICENSE:GPL$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 or (at your option) 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.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-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QKNXSECURECONFIGURATION_H -#define QKNXSECURECONFIGURATION_H - -#include - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QKnxSecureConfigurationPrivate; -class Q_KNX_EXPORT QKnxSecureConfiguration -{ -public: - enum class Type : quint8 - { - Tunneling = 0x00, - DeviceManagement = 001 - }; - - QKnxSecureConfiguration(); - ~QKnxSecureConfiguration(); - - static QVector fromKeyring(QKnxSecureConfiguration::Type type, - const QString &keyring, const QByteArray &password, bool validate); - - static QKnxSecureConfiguration fromKeyring(QKnxSecureConfiguration::Type type, - const QKnxAddress &ia, const QString &keyring, const QByteArray &password, bool validate); - - bool isNull() const; - bool isValid() const; - - QKnxSecureKey publicKey() const; - - QKnxSecureKey privateKey() const; - bool setPrivateKey(const QKnxSecureKey &key); - - QKnxNetIp::SecureUserId userId() const; - bool setUserId(QKnxNetIp::SecureUserId userId); - - QByteArray userPassword() const; - void setUserPassword(const QByteArray &userPassword); - - QKnxAddress individualAddress() const; - bool setIndividualAddress(const QKnxAddress &address); - - QByteArray deviceAuthenticationCode() const; - bool setDeviceAuthenticationCode(const QByteArray &authenticationCode); - - bool isSecureSessionKeepAliveSet() const; - void setKeepSecureSessionAlive(bool keepAlive); - - QKnxSecureConfiguration(const QKnxSecureConfiguration &other); - QKnxSecureConfiguration &operator=(const QKnxSecureConfiguration &other); - - QKnxSecureConfiguration(QKnxSecureConfiguration &&other) Q_DECL_NOTHROW; - QKnxSecureConfiguration &operator=(QKnxSecureConfiguration &&other) Q_DECL_NOTHROW; - - void swap(QKnxSecureConfiguration &other) Q_DECL_NOTHROW; - - bool operator==(const QKnxSecureConfiguration &other) const; - bool operator!=(const QKnxSecureConfiguration &other) const; - -private: - friend class QKnxNetIpEndpointConnection; - friend class QKnxNetIpEndpointConnectionPrivate; - QSharedDataPointer d; -}; -Q_DECLARE_SHARED(QKnxSecureConfiguration) - -QT_END_NAMESPACE - -#endif diff --git a/src/knx/ssl/qknxsecureconfiguration_p.h b/src/knx/ssl/qknxsecureconfiguration_p.h deleted file mode 100644 index 082f3c2..0000000 --- a/src/knx/ssl/qknxsecureconfiguration_p.h +++ /dev/null @@ -1,67 +0,0 @@ -/****************************************************************************** -** -** Copyright (C) 2019 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtKnx module. -** -** $QT_BEGIN_LICENSE:GPL$ -** 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 General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 or (at your option) 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.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-3.0.html. -** -** $QT_END_LICENSE$ -** -******************************************************************************/ - -#ifndef QKNXSECURECONFIGURATION_P_H -#define QKNXSECURECONFIGURATION_P_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt KNX API. It exists for the convenience -// of the Qt KNX implementation. This header file may change from version -// to version without notice, or even be removed. -// -// We mean it. -// - -#include -#include -#include - -QT_BEGIN_NAMESPACE - -class QKnxSecureConfigurationPrivate : public QSharedData -{ -public: - QKnxSecureConfigurationPrivate() = default; - ~QKnxSecureConfigurationPrivate() = default; - - QKnxSecureKey privateKey; - QKnxSecureKey publicKey; - QKnxNetIp::SecureUserId userId { QKnxNetIp::SecureUserId::Reserved }; - QByteArray userPassword; - QKnxAddress ia; - QByteArray deviceAuthenticationCode; - bool keepAlive { false }; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/knx/ssl/ssl.pri b/src/knx/ssl/ssl.pri index e499e10..35f2730 100644 --- a/src/knx/ssl/ssl.pri +++ b/src/knx/ssl/ssl.pri @@ -1,13 +1,10 @@ HEADERS += ssl/qknxcryptographicengine.h \ ssl/qknxsecurekey.h \ - ssl/qknxsecureconfiguration.h \ - ssl/qknxsecureconfiguration_p.h \ ssl/qknxssl_p.h \ ssl/qknxkeyring_p.h SOURCES += ssl/qknxcryptographicengine.cpp \ ssl/qknxsecurekey.cpp \ - ssl/qknxsecureconfiguration.cpp \ ssl/qknxssl_openssl.cpp \ ssl/qknxkeyring.cpp -- cgit v1.2.3