summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-03-05 03:03:30 +0100
committerQt Forward Merge Bot <qt_forward_merge_bot@qt-project.org>2019-03-05 03:03:30 +0100
commit219615393400dfb065908a2be76abf512268dc1e (patch)
tree74426be986e6158fd240965220bd412225e2cc3d
parente304505006b07dde1df094d694a31d65a10a2a32 (diff)
parent0d6a4f6c5973f077670c26edcb01b4c69d5f6ff2 (diff)
Merge remote-tracking branch 'origin/5.13' into dev
-rw-r--r--src/knx/ssl/qknxcryptographicengine.cpp87
-rw-r--r--src/knx/ssl/qknxcryptographicengine.h14
-rw-r--r--src/knx/ssl/qknxkeyring.cpp519
-rw-r--r--src/knx/ssl/qknxkeyring_p.h160
-rw-r--r--src/knx/ssl/qknxsecureconfiguration.cpp149
-rw-r--r--src/knx/ssl/qknxsecureconfiguration.h12
-rw-r--r--src/knx/ssl/qknxsecurekey.cpp4
-rw-r--r--src/knx/ssl/ssl.pri6
8 files changed, 938 insertions, 13 deletions
diff --git a/src/knx/ssl/qknxcryptographicengine.cpp b/src/knx/ssl/qknxcryptographicengine.cpp
index cbffaaa..c4ab26f 100644
--- a/src/knx/ssl/qknxcryptographicengine.cpp
+++ b/src/knx/ssl/qknxcryptographicengine.cpp
@@ -167,7 +167,7 @@ bool QKnxCryptographicEngine::supportsCryptography()
return QKnxSsl::supportsCryptography();
}
-/*
+/*!
Returns the OpenSSL version number of the OpenSSL library if OpenSSL is
available and used to provide cryptographic support; or \c 0 in any other
case.
@@ -213,8 +213,8 @@ QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxByteArray &sharedSec
/*!
Returns the password hash derived from the user chosen password \a password.
- \note The salt used in the Password-Based Key Derivation Function (PBKDF2)
- function is set to \e {user-password.1.secure.ip.knx.org}.
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {user-password.1.secure.ip.knx.org}.
*/
QKnxByteArray QKnxCryptographicEngine::userPasswordHash(const QByteArray &password)
{
@@ -224,11 +224,25 @@ QKnxByteArray QKnxCryptographicEngine::userPasswordHash(const QByteArray &passwo
}
/*!
+ Returns the keyring password hash derived from the user chosen password
+ \a password.
+
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {1.keyring.ets.knx.org}.
+*/
+QKnxByteArray QKnxCryptographicEngine::keyringPasswordHash(const QByteArray &password)
+{
+ const auto hash = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Algorithm::Sha256,
+ password, "1.keyring.ets.knx.org", 0x10000, 16);
+ return QKnxByteArray::fromByteArray(hash);
+}
+
+/*!
Returns the device authentication code hash derived from the user chosen
password \a password.
- \note The salt used in the Password-Based Key Derivation Function (PBKDF2)
- function is set to \e {device-authentication-code.1.secure.ip.knx.org}.
+ \note The salt used in the password-based key derivation function (PBKDF2)
+ is set to \e {device-authentication-code.1.secure.ip.knx.org}.
*/
QKnxByteArray QKnxCryptographicEngine::deviceAuthenticationCodeHash(const QByteArray &password)
{
@@ -238,7 +252,15 @@ QKnxByteArray QKnxCryptographicEngine::deviceAuthenticationCodeHash(const QByteA
}
/*!
- Performs a bytewise XOR operation on the arguments \a left and \a right.
+ Returns the hash of \a data using the \c Sha256 algorithm.
+*/
+QKnxByteArray QKnxCryptographicEngine::hashSha256(const QByteArray &data)
+{
+ return QKnxByteArray::fromByteArray(QCryptographicHash::hash(data, QCryptographicHash::Sha256));
+}
+
+/*!
+ Performs a byte-wise XOR operation on the arguments \a left and \a right.
If the arguments are not equal in size, the function uses only the shorter
array for the operation. If \a adjust is set to \c true, the arrays are made
equal by padding them with \c 0x00 bytes.
@@ -353,6 +375,26 @@ QKnxByteArray QKnxCryptographicEngine::computeMessageAuthenticationCode(const QK
}
/*!
+ Decrypts the given \a data with \a key and the initial vector \a iv. Returns
+ an array of bytes that represents the decrypted data.
+*/
+QKnxByteArray QKnxCryptographicEngine::decrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data)
+{
+ return QKnxSsl::doCrypt(key, iv, data, QKnxSsl::Decrypt);
+}
+
+/*!
+ Encrypts the given \a data with \a key and the initial vector \a iv. Returns
+ an array of bytes that represents the encrypted data.
+*/
+QKnxByteArray QKnxCryptographicEngine::encrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data)
+{
+ return QKnxSsl::doCrypt(key, iv, data, QKnxSsl::Encrypt);
+}
+
+/*!
Encrypts the given KNXnet/IP frame \a frame with the given key \a key,
sequence number \a sequenceNumber, serial number \a serialNumber, and
message tag \a messageTag. Returns an array of bytes that represent the
@@ -416,4 +458,37 @@ QKnxByteArray QKnxCryptographicEngine::decryptMessageAuthenticationCode(const QK
return QKnxPrivate::processMAC(key, mac, sequenceNumber, serialNumber, messageTag);
}
+/*!
+ Decodes and decrypts a tool key \a toolKey that was stored in an ETS
+ keyring (*.knxkeys) file with the given password hash \a passwordHash and
+ created hash \a createdHash.
+
+ Returns an array of bytes that represent the decrypted tool key or an empty
+ byte array in case of an error.
+*/
+QKnxByteArray QKnxCryptographicEngine::decodeAndDecryptToolKey(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash, const QByteArray &toolKey)
+{
+ auto base64 = QKnxByteArray::fromByteArray(QByteArray::fromBase64(toolKey));
+ return QKnxSsl::doCrypt(passwordHash, createdHash, base64, QKnxSsl::Decrypt);
+}
+
+/*!
+ Decodes and decrypts a password \a password that was stored in an ETS
+ keyring (*.knxkeys) file with the given password hash \a passwordHash and
+ created hash \a createdHash.
+
+ Returns an array of bytes that represent the decrypted password or an empty
+ byte array in case of an error.
+*/
+QKnxByteArray QKnxCryptographicEngine::decodeAndDecryptPassword(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash, const QByteArray &password)
+{
+ const auto base64 = QKnxByteArray::fromByteArray(QByteArray::fromBase64(password));
+ const auto decoded = QKnxSsl::doCrypt(passwordHash, createdHash, base64, QKnxSsl::Decrypt);
+
+ quint8 paddedSize = decoded.value(decoded.size() - 1);
+ return decoded.mid(8, decoded.size() - paddedSize - 8);
+}
+
QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxcryptographicengine.h b/src/knx/ssl/qknxcryptographicengine.h
index e509813..eb48816 100644
--- a/src/knx/ssl/qknxcryptographicengine.h
+++ b/src/knx/ssl/qknxcryptographicengine.h
@@ -54,8 +54,10 @@ public:
static QKnxByteArray sessionKey(const QKnxByteArray &sharedSecret);
static QKnxByteArray userPasswordHash(const QByteArray &password);
+ static QKnxByteArray keyringPasswordHash(const QByteArray &password);
static QKnxByteArray deviceAuthenticationCodeHash(const QByteArray &password);
+ static QKnxByteArray hashSha256(const QByteArray &data);
static QKnxByteArray XOR(const QKnxByteArray &l, const QKnxByteArray &r, bool adjust = true);
static QKnxByteArray computeMessageAuthenticationCode(const QKnxByteArray &key,
@@ -66,6 +68,11 @@ public:
const QKnxByteArray &serialNumber = {},
quint16 messageTag = 0);
+ static QKnxByteArray decrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data);
+ static QKnxByteArray encrypt(const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data);
+
static QKnxByteArray encryptSecureWrapperPayload(const QKnxByteArray &key,
const QKnxNetIpFrame &frame,
quint48 sequenceNumber,
@@ -89,6 +96,13 @@ public:
quint48 sequenceNumber = 0,
const QKnxByteArray &serialNumber = {},
quint16 messageTag = 0);
+
+ static QKnxByteArray decodeAndDecryptToolKey(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash,
+ const QByteArray &toolKey);
+ static QKnxByteArray decodeAndDecryptPassword(const QKnxByteArray &passwordHash,
+ const QKnxByteArray &createdHash,
+ const QByteArray &password);
};
QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxkeyring.cpp b/src/knx/ssl/qknxkeyring.cpp
new file mode 100644
index 0000000..02bd6b6
--- /dev/null
+++ b/src/knx/ssl/qknxkeyring.cpp
@@ -0,0 +1,519 @@
+/******************************************************************************
+**
+** 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 "private/qknxkeyring_p.h"
+#include "private/qknxssl_p.h"
+
+#include <QtCore/qcryptographichash.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qxmlstream.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+ \namespace QKnx::Ets
+*/
+
+/*!
+ \internal
+ \namespace QKnx::Ets::Keyring
+*/
+
+namespace QKnx { namespace Ets { namespace Keyring {
+
+namespace QKnxPrivate
+{
+ void writeBytes(QByteArray *dest, const QStringRef &source)
+ {
+ dest->append(quint8(source.size()));
+ dest->append(source.toString().toUtf8());
+ }
+
+ void writeBytes(QByteArray *dest, const QByteArray &source)
+ {
+ dest->append(quint8(source.size()));
+ dest->append(source);
+ }
+
+ static void readElement(QXmlStreamReader *reader, QByteArray *bytes)
+ {
+ bytes->append(0x01);
+ QKnxPrivate::writeBytes(bytes, reader->name());
+
+ auto attributes = reader->attributes();
+ std::sort(attributes.begin(), attributes.end(),
+ [](const QXmlStreamAttribute &lhs, const QXmlStreamAttribute &rhs) {
+ return lhs.name() < rhs.name();
+ });
+
+ for (auto attribute : qAsConst(attributes)) {
+ QKnxPrivate::writeBytes(bytes, attribute.name());
+ QKnxPrivate::writeBytes(bytes, attribute.value());
+ }
+
+ while (!reader->atEnd()) {
+ auto token = reader->readNext();
+ if (token == QXmlStreamReader::StartElement)
+ QKnxPrivate::readElement(reader, bytes);
+ if (token == QXmlStreamReader::EndElement)
+ bytes->append(0x02);
+ }
+ }
+
+ static bool fetchAttr(const QXmlStreamAttributes &attributes, const QString &attrName,
+ QStringRef *value, QXmlStreamReader *reader)
+ {
+ if (!value || !reader)
+ return false;
+
+ *value = attributes.value(attrName);
+ if (value->isNull())
+ reader->raiseError(QKnxKeyring::tr("Invalid or empty attribute '%1'.").arg(attrName));
+ return !reader->hasError();
+ }
+
+ static bool setString(const QString &name, const QStringRef &attr, int maxSize,
+ QString *field, QXmlStreamReader *reader, bool pedantic)
+ {
+ if (!reader || !field)
+ return false;
+
+ if (pedantic && attr.size() > maxSize) {
+ reader->raiseError(QKnxKeyring::tr("Pedantic error: Invalid value for attribute '%1', "
+ "maximum length is %2 characters, got: '%3'.").arg(name).arg(maxSize).arg(attr.size()));
+ } else {
+ *field = attr.toString();
+ }
+ return !reader->hasError();
+ }
+
+ bool setString(const QString &name, const QStringRef &attr, const QStringList &list,
+ QString *field, QXmlStreamReader *reader, bool pedantic)
+ {
+ if (!reader || !field)
+ return false;
+
+ if (pedantic && !list.contains(attr.toString())) {
+ reader->raiseError(QKnxKeyring::tr("Pedantic error: Invalid value for attribute '%1', "
+ "expected '%2', got: '%3'.").arg(name, list.join(QLatin1String(", "))));
+ } else {
+ *field = attr.toString();
+ }
+ return !reader->hasError();
+ }
+}
+
+const char oneBlockBase64[29] = "^[A-Za-z0-9\\+/]{21}[AQgw]==$";
+// const char twoBlockBase64[40] = "^[A-Za-z0-9\\+/]{42}[AEIMQUYcgkosw048]=$";
+
+const char multicast[128] = "^2(2[4-9]|3[0-9])\\.((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|"
+ "[0-9])\\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$";
+
+const char individualAddress[74] = "^((1[0-5]|[0-9])\\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]"
+ "[0-9]|[0-9])$";
+
+bool QKnxBackbone::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Backbone")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("MulticastAddress"), &attr, reader))
+ return false;
+ MulticastAddress = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Latency"), &attr, reader))
+ return false;
+ Latency = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Key"), &attr, reader))
+ return false;
+ Key = attr.toUtf8();
+
+ if (pedantic) {
+ if (!QRegularExpression(QLatin1String(multicast)).match(MulticastAddress).hasMatch()) {
+ reader->raiseError(tr("The 'MulticastAddress' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(MulticastAddress));
+ return false;
+ }
+
+ if (Latency > 8000) {
+ reader->raiseError(tr("The 'Latency' attribute is invalid. The MaxInclusive "
+ "constraint failed, got: '%1', MaxInclusive: '8000'.").arg(Latency));
+ return false;
+ }
+
+ if (!QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Key' attribute is invalid. The Pattern constraint "
+ "failed, got: '%1'.").arg(QString::fromUtf8(Key)));
+ return false;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Backbone>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxInterface::QKnxGroup::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Group")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Address"), &attr, reader))
+ return false;
+ Address = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Senders"), &attr, reader))
+ return false;
+ Senders = attr.toString().split(QLatin1Char(' ')).toVector();
+ if (pedantic) {
+ QRegularExpression regExp;
+ regExp.setPattern(QLatin1String(individualAddress));
+ for (auto sender : qAsConst(Senders)) {
+ if (!regExp.match(sender).hasMatch()) {
+ reader->raiseError(tr("The 'Senders' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(sender));
+ return false;
+ }
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Group>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxInterface::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Interface")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attribute
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("Type"), &attr, reader))
+ return false;
+ if (!QKnxPrivate::setString(QLatin1String("Type"), attr, {
+ QStringLiteral("Backbone"),
+ QStringLiteral("Tunneling"),
+ QStringLiteral("USB")
+ }, &Type, reader, pedantic)) return false;
+
+ // optional attributes, TODO: pedantic
+ Host = attrs.value(QStringLiteral("Host")).toString();
+ IndividualAddress = attrs.value(QStringLiteral("IndividualAddress")).toString();
+ UserID = attrs.value(QStringLiteral("UserID")).toUShort();
+ Password = attrs.value(QStringLiteral("Password")).toUtf8();
+ Authentication = attrs.value(QStringLiteral("Authentication")).toUtf8();
+
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Group")) {
+ QKnxInterface::QKnxGroup group;
+ if (!group.parseElement(reader, pedantic))
+ return false;
+ Group.append(group);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Interface"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Interface>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxGroupAddresses::QKnxGroup::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Group")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Address"), &attr, reader))
+ return false;
+ Address = attr.toUShort();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Key"), &attr, reader))
+ return false;
+ Key = attr.toUtf8();
+ if (pedantic && !QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Key' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(QString::fromUtf8(Key)));
+ return false;
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Group>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxGroupAddresses::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("GroupAddresses")) {
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Group")) {
+ QKnxGroupAddresses::QKnxGroup group;
+ if (!group.parseElement(reader, pedantic))
+ return false;
+ Group.append(group);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("GroupAddresses"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <GroupAddresses>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError() && Group.size() >= 1;
+}
+
+bool QKnxDevice::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Device")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attribute
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("IndividualAddress"), &attr, reader))
+ return false;
+ IndividualAddress = attr.toString();
+ if (pedantic && !QRegularExpression(QLatin1String(individualAddress))
+ .match(IndividualAddress).hasMatch()) {
+ reader->raiseError(tr("The 'IndividualAddress' attribute is invalid. The "
+ "Pattern constraint failed, got: '%1'.").arg(IndividualAddress));
+ return false;
+ }
+
+ // optional attributes, TODO: pedantic
+ ToolKey = attrs.value(QStringLiteral("ToolKey")).toUtf8();
+ SequenceNumber = attrs.value(QStringLiteral("SequenceNumber")).toULongLong();
+ ManagementPassword = attrs.value(QStringLiteral("ManagementPassword")).toUtf8();
+ Authentication = attrs.value(QStringLiteral("Authentication")).toUtf8();
+ } else {
+ reader->raiseError(tr("Expected element <Device>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxDevices::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->isStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Devices")) {
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Device")) {
+ QKnxDevice device;
+ if (!device.parseElement(reader, pedantic))
+ return false;
+ Device.append(device);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Devices"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Devices>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError() && Device.size() >= 1;
+}
+
+bool QKnxKeyring::parseElement(QXmlStreamReader *reader, bool pedantic)
+{
+ if (!reader || !reader->readNextStartElement())
+ return false;
+
+ if (reader->name() == QStringLiteral("Keyring")) {
+ auto attrs = reader->attributes();
+
+ QStringRef attr; // mandatory attributes
+ if (!QKnxPrivate::fetchAttr(attrs, QLatin1String("Project"), &attr, reader))
+ return false;
+ if (!QKnxPrivate::setString(QLatin1String("Project"), attr, 255, &Project, reader, pedantic))
+ return false;
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("CreatedBy"), &attr, reader))
+ return false;
+ CreatedBy = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Created"), &attr, reader))
+ return false;
+ Created = attr.toString();
+
+ if (!QKnxPrivate::fetchAttr(attrs, QStringLiteral("Signature"), &attr, reader))
+ return false;
+ Signature = attr.toUtf8();
+ if (pedantic) {
+ if (!QRegularExpression(QLatin1String(oneBlockBase64)).match(attr).hasMatch()) {
+ reader->raiseError(tr("The 'Signature' attribute is invalid. The Pattern "
+ "constraint failed, got: '%1'.").arg(attr));
+ return false;
+ }
+ }
+
+ // children
+ while (!reader->atEnd() && !reader->hasError()) {
+ auto tokenType = reader->readNext();
+ if (tokenType == QXmlStreamReader::TokenType::StartElement) {
+ if (reader->name() == QStringLiteral("Backbone")) {
+ if (pedantic && Backbone.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element <Backbone> "
+ "more than once."));
+ return false;
+ }
+ QKnxBackbone backbone;
+ if (!backbone.parseElement(reader, pedantic))
+ return false;
+ Backbone.append(backbone);
+ } else if (reader->name() == QStringLiteral("Interface")) {
+ QKnxInterface interface;
+ if (!interface.parseElement(reader, pedantic))
+ return false;
+ Interface.append(interface);
+ } else if (reader->name() == QStringLiteral("GroupAddresses")) {
+ if (pedantic && GroupAddresses.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element "
+ "<GroupAddresses> more than once."));
+ return false;
+ }
+ QKnxGroupAddresses groupAddresses;
+ if (!groupAddresses.parseElement(reader, pedantic))
+ return false;
+ GroupAddresses.append(groupAddresses);
+ } else if (reader->name() == QStringLiteral("Devices")) {
+ if (pedantic && Devices.size() >= 1) {
+ reader->raiseError(tr("Pedantic error: Encountered element "
+ "<Devices> more than once."));
+ return false;
+ }
+ QKnxDevices devices;
+ if (!devices.parseElement(reader, pedantic))
+ return false;
+ Devices.append(devices);
+ }
+ } else if (tokenType == QXmlStreamReader::TokenType::EndElement) {
+ if (reader->name() == QLatin1String("Keyring"))
+ break;
+ }
+ }
+ } else {
+ reader->raiseError(tr("Expected element <Keyring>, got: <%1>.").arg(reader->name()
+ .toString()));
+ }
+ return !reader->hasError();
+}
+
+bool QKnxKeyring::validate(QXmlStreamReader *reader, const QKnxByteArray &pwHash) const
+{
+ if (!reader || !reader->readNextStartElement())
+ return false;
+
+ if (reader->name() != QStringLiteral("Keyring"))
+ return false;
+
+ QByteArray bytes;
+ bytes.append(0x01);
+
+ QKnxPrivate::writeBytes(&bytes, reader->name());
+
+ auto attributes = reader->attributes();
+ if (attributes.isEmpty())
+ return false;
+
+ auto signature = attributes.value({}, QStringLiteral("Signature")).toUtf8();
+ if (signature.isEmpty())
+ return false;
+
+ std::sort(attributes.begin(), attributes.end(),
+ [](const QXmlStreamAttribute &lhs, const QXmlStreamAttribute &rhs) {
+ return lhs.name() < rhs.name();
+ });
+
+ for (auto attribute : attributes) {
+ if (attribute.name() == QStringLiteral("xmlns"))
+ continue;
+ if (attribute.name() == QStringLiteral("Signature"))
+ continue;
+
+ QKnxPrivate::writeBytes(&bytes, attribute.name());
+ QKnxPrivate::writeBytes(&bytes, attribute.value());
+ }
+
+ if (reader->readNextStartElement())
+ QKnxPrivate::readElement(reader, &bytes);
+ QKnxPrivate::writeBytes(&bytes, pwHash.toByteArray().toBase64());
+
+ return QKnxCryptographicEngine::hashSha256(bytes).left(16)
+ == QKnxByteArray::fromByteArray(QByteArray::fromBase64(signature));
+}
+
+}}} // QKnx::Ets::Keyring
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxkeyring_p.h b/src/knx/ssl/qknxkeyring_p.h
new file mode 100644
index 0000000..752131b
--- /dev/null
+++ b/src/knx/ssl/qknxkeyring_p.h
@@ -0,0 +1,160 @@
+/******************************************************************************
+**
+** 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 QKNXKEYRING_P_H
+#define QKNXKEYRING_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 <QtCore/qcoreapplication.h>
+#include <QtCore/qvector.h>
+#include <QtCore/qxmlstream.h>
+
+#include <QtKnx/qtknxglobal.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QKnx { namespace Ets { namespace Keyring {
+
+struct Q_KNX_EXPORT QKnxBackbone
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxBackbone)
+
+public:
+ QString MulticastAddress; // mandatory, pattern 2(2[4-9]|3[0-9])\.((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ quint16 Latency; // mandatory, min. value 0, 8000 value max.
+ QByteArray Key; // mandatory, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxInterface
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxInterface)
+
+public:
+ QString Type; // mandatory, Backbone, Tunneling, USB
+
+ QString Host; // optional
+ QString IndividualAddress; // optional, pattern ((1[0-5]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ quint8 UserID; // optional
+ QByteArray Password; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+ QByteArray Authentication; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+
+ struct Q_KNX_EXPORT QKnxGroup
+ {
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroup)
+
+ public:
+ quint16 Address; // mandatory
+ QVector<QString> Senders; // mandatory
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ };
+ QVector<QKnxGroup> Group; // 0..n
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxGroupAddresses
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroupAddresses)
+
+public:
+ struct Q_KNX_EXPORT QKnxGroup
+ {
+ Q_DECLARE_TR_FUNCTIONS(QKnxGroup)
+
+ public:
+ quint16 Address; // mandatory
+ QByteArray Key; // mandatory, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ };
+ QVector<QKnxGroup> Group; // 1..n
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxDevice
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxDevice)
+
+public:
+ QString IndividualAddress; //mandatory, pattern ((1[0-5]|[0-9])\.){2}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
+ QByteArray ToolKey; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+ quint48 SequenceNumber; // optional
+ QByteArray ManagementPassword; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+ QByteArray Authentication; // optional, Base64 encoded, pattern [A-Za-z0-9\+/]{42}[AEIMQUYcgkosw048]=
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxDevices
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxDevices)
+
+public:
+ QVector<QKnxDevice> Device; // 0..n
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+};
+
+struct Q_KNX_EXPORT QKnxKeyring
+{
+ Q_DECLARE_TR_FUNCTIONS(QKnxKeyring)
+
+public:
+ QString Project; // mandatory, 50 character max.
+ QString Created; // mandatory, xs:dateTime
+ QString CreatedBy; // mandatory
+ QByteArray Signature; // mandatory, Base64 encoded, pattern [A-Za-z0-9\+/]{21}[AQgw]==
+
+ QVector<QKnxBackbone> Backbone; // 0..1
+ QVector<QKnxInterface> Interface; // 0..n
+ QVector<QKnxGroupAddresses> GroupAddresses; // 0..1
+ QVector<QKnxDevices> Devices; // 0..1
+
+ bool parseElement(QXmlStreamReader *reader, bool pedantic);
+ bool validate(QXmlStreamReader *reader, const QKnxByteArray &pwHash) const;
+};
+
+}}} // QKnx::Ets::Keyring
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/knx/ssl/qknxsecureconfiguration.cpp b/src/knx/ssl/qknxsecureconfiguration.cpp
index bd5a57a..8258c94 100644
--- a/src/knx/ssl/qknxsecureconfiguration.cpp
+++ b/src/knx/ssl/qknxsecureconfiguration.cpp
@@ -27,11 +27,109 @@
**
****************************************************************************/
+#include "qknxcryptographicengine.h"
#include "qknxsecureconfiguration.h"
+
+#include <QtCore/qfile.h>
+
+#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<QKnxSecureConfiguration> 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<QKnxSecureConfiguration> 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
@@ -51,6 +149,18 @@ QT_BEGIN_NAMESPACE
*/
/*!
+ \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()
@@ -65,6 +175,39 @@ QKnxSecureConfiguration::QKnxSecureConfiguration()
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> 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.
@@ -177,6 +320,7 @@ QKnxAddress QKnxSecureConfiguration::individualAddress() const
/*!
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
@@ -184,10 +328,9 @@ QKnxAddress QKnxSecureConfiguration::individualAddress() const
*/
bool QKnxSecureConfiguration::setIndividualAddress(const QKnxAddress &address)
{
- auto isIa = address.type() == QKnxAddress::Type::Individual;
- if (isIa)
+ if ((address.type() == QKnxAddress::Type::Individual) || (!address.isValid()))
d->ia = address;
- return isIa;
+ return d->ia == address;
}
/*!
diff --git a/src/knx/ssl/qknxsecureconfiguration.h b/src/knx/ssl/qknxsecureconfiguration.h
index 5592a47..6dd0e47 100644
--- a/src/knx/ssl/qknxsecureconfiguration.h
+++ b/src/knx/ssl/qknxsecureconfiguration.h
@@ -42,9 +42,21 @@ class QKnxSecureConfigurationPrivate;
class Q_KNX_EXPORT QKnxSecureConfiguration
{
public:
+ enum class Type : quint8
+ {
+ Tunneling = 0x00,
+ DeviceManagement = 001
+ };
+
QKnxSecureConfiguration();
~QKnxSecureConfiguration();
+ static QVector<QKnxSecureConfiguration> 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;
diff --git a/src/knx/ssl/qknxsecurekey.cpp b/src/knx/ssl/qknxsecurekey.cpp
index 99e1e50..953d126 100644
--- a/src/knx/ssl/qknxsecurekey.cpp
+++ b/src/knx/ssl/qknxsecurekey.cpp
@@ -33,8 +33,8 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#if QT_CONFIG(opensslv11)
-# include <QtKnx/private/qsslsocket_openssl_symbols_p.h>
-# include <QtKnx/private/qsslsocket_openssl11_symbols_p.h>
+# include "private/qsslsocket_openssl_symbols_p.h"
+# include "private/qsslsocket_openssl11_symbols_p.h"
#endif
QT_BEGIN_NAMESPACE
diff --git a/src/knx/ssl/ssl.pri b/src/knx/ssl/ssl.pri
index 112d7fb..e499e10 100644
--- a/src/knx/ssl/ssl.pri
+++ b/src/knx/ssl/ssl.pri
@@ -2,12 +2,14 @@ HEADERS += ssl/qknxcryptographicengine.h \
ssl/qknxsecurekey.h \
ssl/qknxsecureconfiguration.h \
ssl/qknxsecureconfiguration_p.h \
- ssl/qknxssl_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/qknxssl_openssl.cpp \
+ ssl/qknxkeyring.cpp
qtConfig(opensslv11) { # OpenSSL 1.1 support is required.
SOURCES += ssl/qsslsocket_openssl_symbols.cpp