summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarsten Heimrich <karsten.heimrich@qt.io>2018-08-20 16:21:23 +0200
committerKarsten Heimrich <karsten.heimrich@qt.io>2018-08-22 13:35:16 +0000
commit24015b247b4b32db227cd2070d90dafa5d71d556 (patch)
treecd899a75f902299d168c90c712fe31ded2e1add6
parent081ed0ba7c9a96a544d394611854f38660c88062 (diff)
Fix KNX cryptographic engine implementation
Change-Id: Iffff0e4157467a38f842a87001f4216f4c87d17a Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
-rw-r--r--src/knx/ssl/qknxcurve25519.cpp188
-rw-r--r--src/knx/ssl/qknxcurve25519.h30
-rw-r--r--src/knx/ssl/qsslsocket_openssl_symbols.cpp21
-rw-r--r--src/knx/ssl/qsslsocket_openssl_symbols_p.h23
-rw-r--r--tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp53
5 files changed, 311 insertions, 4 deletions
diff --git a/src/knx/ssl/qknxcurve25519.cpp b/src/knx/ssl/qknxcurve25519.cpp
index 4d53a3c..abb1dc5 100644
--- a/src/knx/ssl/qknxcurve25519.cpp
+++ b/src/knx/ssl/qknxcurve25519.cpp
@@ -30,6 +30,15 @@
#include "qknxcurve25519.h"
#include "qknxcryptographicdata_p.h"
+#include "qknxnetipsessionauthenticate.h"
+#include "qknxnetipsecurewrapper.h"
+#include "qknxnetipsessionrequest.h"
+#include "qknxnetipsessionresponse.h"
+#include "qknxnetipsessionstatus.h"
+#include "qknxnetiptimernotify.h"
+
+#include <QtCore/qcryptographichash.h>
+
QT_BEGIN_NAMESPACE
bool QKnxOpenSsl::s_libraryLoaded = false;
@@ -209,7 +218,10 @@ QKnxByteArray QKnxCurve25519PublicKey::bytes() const
QKnxCurve25519PublicKey QKnxCurve25519PublicKey::fromBytes(const QKnxByteArray &data, quint16 index)
{
auto ba = data.mid(index, 32);
- if (!qt_QKnxOpenSsl->supportsSsl() || ba.size() < 32)
+ if (ba.size() < 32)
+ return {};
+
+ if (!qt_QKnxOpenSsl->supportsSsl())
return {};
QKnxCurve25519PublicKey key;
@@ -392,10 +404,10 @@ QKnxCurve25519PrivateKey &QKnxCurve25519PrivateKey::operator=(const QKnxCurve255
QKnxByteArray QKnxCryptographicEngine::sharedSecret(const QKnxCurve25519PublicKey &pub,
const QKnxCurve25519PrivateKey &priv)
{
- if (!qt_QKnxOpenSsl->supportsSsl())
+ if (pub.isNull() || priv.isNull())
return {};
- if (pub.isNull() || priv.isNull())
+ if (!qt_QKnxOpenSsl->supportsSsl())
return {};
auto evpPKeyCtx = q_EVP_PKEY_CTX_new(priv.d_ptr->m_evpPKey, nullptr);
@@ -426,5 +438,173 @@ QKnxByteArray QKnxCryptographicEngine::sharedSecret(const QKnxCurve25519PublicKe
return ba;
}
-QT_END_NAMESPACE
+QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxByteArray &sharedSecret)
+{
+ if (sharedSecret.isEmpty())
+ return {};
+
+ return QKnxByteArray::fromByteArray(QCryptographicHash::hash(sharedSecret.toByteArray(),
+ QCryptographicHash::Sha256)).mid(0, 16);
+}
+
+QKnxByteArray QKnxCryptographicEngine::sessionKey(const QKnxCurve25519PublicKey &pub,
+ const QKnxCurve25519PrivateKey & priv)
+{
+ return sessionKey(sharedSecret(pub, priv));
+}
+
+QKnxByteArray QKnxCryptographicEngine::userPasswordHash(const QByteArray &password)
+{
+ return pkcs5Pbkdf2HmacSha256(password,
+ QKnxByteArray("user-password.1.secure.ip.knx.org", 33), 0x10000, 16);
+}
+
+QKnxByteArray QKnxCryptographicEngine::deviceAuthenticationCodeHash(const QByteArray &password)
+{
+ return pkcs5Pbkdf2HmacSha256(password,
+ QKnxByteArray("device-authentication-code.1.secure.ip.knx.org", 46), 0x10000, 16);
+}
+
+namespace QKnxPrivate
+{
+ static QKnxByteArray xor(const QKnxByteArray &left, const QKnxByteArray &right)
+ {
+ QKnxByteArray result(qMax(left.size(), right.size()), Qt::Uninitialized);
+ for (int i = result.size() - 1; i >= 0; --i)
+ result.set(i, left.value(i, 0x00) ^ right.value(i, 0x00));
+ return result;
+ }
+
+ static QKnxByteArray doCrypt(int mode, const QKnxByteArray &key, const QKnxByteArray &iv,
+ const QKnxByteArray &data)
+ {
+ QSharedPointer<EVP_CIPHER_CTX> ctxPtr(q_EVP_CIPHER_CTX_new(), q_EVP_CIPHER_CTX_free);
+ if (ctxPtr.isNull())
+ return {};
+
+ const auto ctx = ctxPtr.data();
+ if (q_EVP_CipherInit_ex(ctx, q_EVP_aes_128_cbc(), nullptr, nullptr, nullptr, mode) <= 0)
+ return {};
+
+ if (q_EVP_CIPHER_CTX_set_padding(ctx, 0) <= 0)
+ return {};
+
+ Q_ASSERT(q_EVP_CIPHER_CTX_iv_length(ctx) == 16);
+ Q_ASSERT(q_EVP_CIPHER_CTX_key_length(ctx) == 16);
+ if (q_EVP_CipherInit_ex(ctx, nullptr, nullptr, key.constData(), iv.constData(), mode) <= 0)
+ return {};
+
+ int outl;
+ QKnxByteArray out(EVP_MAX_BLOCK_LENGTH, 0x00);
+ if (q_EVP_CipherUpdate(ctx, out.data(), &outl, data.constData(), data.size()) <= 0)
+ return {};
+
+ int outlen;
+ if (q_EVP_CipherFinal_ex(ctx, out.data() + outl, &outlen) <= 0)
+ return {};
+
+ return out.mid(0, outl + outlen);
+ }
+}
+
+QKnxByteArray QKnxCryptographicEngine::messageAuthenticationCode(QKnxCryptographicEngine::Mode mode,
+ quint16 secureId, const QKnxNetIpFrameHeader &frameHdr, const QKnxCurve25519PublicKey &publicKey,
+ const QKnxCurve25519PublicKey &peerKey, const QKnxByteArray &deviceAuthenticationCode)
+{
+ if (mode > QKnxCryptographicEngine::Mode::Encrypt
+ || !frameHdr.isValid()
+ || publicKey.isNull() || peerKey.isNull()
+ || deviceAuthenticationCode.isEmpty()) {
+ return {};
+ }
+
+ QKnxByteArray iv, ctr0; // initialization vector, block counter
+ if (frameHdr.serviceType() == QKnxNetIp::ServiceType::SecureWrapper) {
+ // |--------------------------------------------------------------------|
+ // | 0 | ... | 5 | 6 | ... | 11 | 12 | 13 | 14 | 15 |
+ // |--------------------------------------------------------------------|
+ // | Sequence info | Serial number | Tag | Q |
+ // |--------------------------------------------------------------------|
+
+ // Q Shall be the length of the payload in octets, which is the length of
+ // the original, encapsulated KNXnet/IP frame.
+ iv = QKnxByteArray { /* TODO: Implement the scheme above */ };
+
+ // |--------------------------------------------------------------------|
+ // | 0 | ... | 5 | 6 | ... | 11 | 12 | 13 | 14 | 15 |
+ // |--------------------------------------------------------------------|
+ // | Sequence info | Serial number | Tag | ff | i |
+ // |--------------------------------------------------------------------|
+
+ // For Ctr0 the counter [i] shall be 00h initially. Each counter value [i]
+ // shall be calculated by incrementing the preceding counter value by 1.
+ ctr0 = QKnxByteArray { /* TODO: Implement the scheme above */ };
+ } else if (frameHdr.serviceType() == QKnxNetIp::ServiceType::SessionResponse
+ || frameHdr.serviceType() == QKnxNetIp::ServiceType::SessionAuthenticate) {
+ iv = QKnxByteArray { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00 };
+ ctr0 = QKnxByteArray { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0xff, 0x00 };
+ } else if (frameHdr.serviceType() == QKnxNetIp::ServiceType::TimerNotify) {
+ // |--------------------------------------------------------------------|
+ // | 0 | ... | 5 | 6 | ... | 11 | 12 | 13 | 14 | 15 |
+ // |--------------------------------------------------------------------|
+ // | Timer value | Serial number | Tag | Q = 0000h |
+ // |--------------------------------------------------------------------|
+ iv = QKnxByteArray { /* TODO: Implement the scheme above */ };
+
+ // |--------------------------------------------------------------------|
+ // | 0 | ... | 5 | 6 | ... | 11 | 12 | 13 | 14 | 15 |
+ // |--------------------------------------------------------------------|
+ // | Timer value | Serial number | Tag | ff | 00h |
+ // |--------------------------------------------------------------------|
+ ctr0 = QKnxByteArray { /* TODO: Implement the scheme above */ };
+ }
+
+ if (iv.isEmpty() || ctr0.isEmpty())
+ return {};
+
+ auto header = frameHdr.bytes();
+ auto xor = QKnxPrivate::xor (publicKey.bytes(), peerKey.bytes());
+ auto sessionId = QKnxUtils::QUint16::bytes(quint16(secureId));
+
+ auto A = QKnxByteArray({ 0x00, 0x28 }) + header + sessionId + xor;
+ A.resize(48); // pad
+
+ if (!qt_QKnxOpenSsl->supportsSsl())
+ return {};
+
+ auto B0 = iv;
+ auto B1 = A.mid(0, 16);
+ auto B2 = A.mid(16, 16);
+ auto B3 = A.mid(32, 16);
+
+ int m = int(mode);
+ auto Y0 = QKnxPrivate::doCrypt(m, deviceAuthenticationCode, iv, QKnxPrivate::xor(B0, { 0x00 }));
+ auto Y1 = QKnxPrivate::doCrypt(m, deviceAuthenticationCode, iv, QKnxPrivate::xor(B1, Y0));
+ auto Y2 = QKnxPrivate::doCrypt(m, deviceAuthenticationCode, iv, QKnxPrivate::xor(B2, Y1));
+ auto Y3 = QKnxPrivate::doCrypt(m, deviceAuthenticationCode, iv, QKnxPrivate::xor(B3, Y2));
+ auto S0 = QKnxPrivate::doCrypt(m, deviceAuthenticationCode, iv, ctr0);
+
+ return QKnxPrivate::xor(S0, Y3);
+}
+
+QKnxByteArray QKnxCryptographicEngine::pkcs5Pbkdf2HmacSha256(const QByteArray &password,
+ const QKnxByteArray &salt, qint32 iterations, quint8 derivedKeyLength)
+{
+ if (derivedKeyLength > 32)
+ return {};
+
+ if (!qt_QKnxOpenSsl->supportsSsl())
+ return {};
+
+ QKnxByteArray out(derivedKeyLength, 0x00);
+ if (q_PKCS5_PBKDF2_HMAC(password.constData(), password.size(), salt.constData(), salt.size(),
+ iterations, q_EVP_sha256(), out.size(), out.data()) <= 0) {
+ return {};
+ }
+ return out;
+}
+
+QT_END_NAMESPACE
diff --git a/src/knx/ssl/qknxcurve25519.h b/src/knx/ssl/qknxcurve25519.h
index 864c842..1bb4372 100644
--- a/src/knx/ssl/qknxcurve25519.h
+++ b/src/knx/ssl/qknxcurve25519.h
@@ -34,6 +34,7 @@
#include <QtKnx/qknxbytearray.h>
#include <QtKnx/qtknxglobal.h>
+#include <QtKnx/qknxnetipframe.h>
QT_BEGIN_NAMESPACE
@@ -86,13 +87,42 @@ private:
class Q_KNX_EXPORT QKnxCryptographicEngine final
{
public:
+ enum class Mode : quint8 {
+ Decrypt = 0x00,
+ Encrypt = 0x01
+ };
+
QKnxCryptographicEngine() = delete;
~QKnxCryptographicEngine() = default;
static QKnxByteArray sharedSecret(const QKnxCurve25519PublicKey &pub,
const QKnxCurve25519PrivateKey &priv);
+
+ static QKnxByteArray sessionKey(const QKnxByteArray &sharedSecret);
+ static QKnxByteArray sessionKey(const QKnxCurve25519PublicKey &pub,
+ const QKnxCurve25519PrivateKey &priv);
+
+ static QKnxByteArray userPasswordHash(const QByteArray &password);
+ static QKnxByteArray deviceAuthenticationCodeHash(const QByteArray &password);
+
+ static QKnxByteArray messageAuthenticationCode(QKnxCryptographicEngine::Mode mode,
+ quint16 secureSessionId,
+ const QKnxNetIpFrameHeader &header,
+ const QKnxCurve25519PublicKey &client,
+ const QKnxCurve25519PublicKey &server,
+ const QKnxByteArray &deviceAuthenticationCode);
+
+ static QKnxByteArray pkcs5Pbkdf2HmacSha256(const QByteArray &password, const QKnxByteArray &salt,
+ qint32 iterations, quint8 derivedKeyLength);
};
QT_END_NAMESPACE
#endif
+
+/*
+b752be246459260f6b0c4801fbd5a67599f83b4057b3ef1e79e469ac17234e15
+b752be246459260f6b0c4801fbd5a67599f83b4057b3ef1e79e469ac17234e15
+
+061009520038 0001 b752be246459260f6b0c4801fbd5a67599f83b4057b3ef1e79e469ac17234e15
+*/
diff --git a/src/knx/ssl/qsslsocket_openssl_symbols.cpp b/src/knx/ssl/qsslsocket_openssl_symbols.cpp
index 5caf7e8..84c9bf6 100644
--- a/src/knx/ssl/qsslsocket_openssl_symbols.cpp
+++ b/src/knx/ssl/qsslsocket_openssl_symbols.cpp
@@ -570,6 +570,17 @@ DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG)
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 **ppkey, ppkey, return 0, return)
+
+ DEFINEFUNC(int, EVP_CIPHER_CTX_key_length, const EVP_CIPHER_CTX *ctx, ctx, return 0, return)
+ DEFINEFUNC(int, EVP_CIPHER_CTX_iv_length, const EVP_CIPHER_CTX *ctx, ctx, return 0, return)
+ DEFINEFUNC3(int, EVP_CipherFinal_ex, EVP_CIPHER_CTX *ctx, ctx, unsigned char *outm, outm, int *outl, outl, return 0, return)
+
+ DEFINEFUNC(const EVP_CIPHER *, EVP_aes_128_cbc, DUMMYARG, DUMMYARG, return nullptr, return)
+ DEFINEFUNC2(int, EVP_CIPHER_CTX_set_padding, EVP_CIPHER_CTX *x, x, int padding, padding, return 0, return)
+
+ DEFINEFUNC8(int, PKCS5_PBKDF2_HMAC, const char *pass, pass, int passlen, passlen, const unsigned char *salt, salt, \
+ int saltlen, saltlen, int iter, iter, const EVP_MD *digest, digest, int keylen, keylen, unsigned char *out, out, return 0, return)
+ DEFINEFUNC(const EVP_MD *, EVP_sha256, DUMMYARG, DUMMYARG, return nullptr, return)
#endif
#define RESOLVEFUNC(func) \
@@ -1283,6 +1294,16 @@ bool q_resolveOpenSslSymbols()
RESOLVEFUNC(EVP_PKEY_keygen_init)
RESOLVEFUNC(EVP_PKEY_keygen)
+
+ RESOLVEFUNC(EVP_CIPHER_CTX_key_length)
+ RESOLVEFUNC(EVP_CIPHER_CTX_iv_length)
+ RESOLVEFUNC(EVP_CipherFinal_ex)
+
+ RESOLVEFUNC(EVP_aes_128_cbc)
+ RESOLVEFUNC(EVP_CIPHER_CTX_set_padding)
+
+ RESOLVEFUNC(PKCS5_PBKDF2_HMAC)
+ RESOLVEFUNC(EVP_sha256)
#endif
symbolsResolved = true;
diff --git a/src/knx/ssl/qsslsocket_openssl_symbols_p.h b/src/knx/ssl/qsslsocket_openssl_symbols_p.h
index 1efe31c..f256638 100644
--- a/src/knx/ssl/qsslsocket_openssl_symbols_p.h
+++ b/src/knx/ssl/qsslsocket_openssl_symbols_p.h
@@ -196,6 +196,18 @@ QT_BEGIN_NAMESPACE
funcret _q_##func(a, b, c, d, e, f, g); \
}
+// ret func(arg1, arg2, arg3, arg4, arg6, arg7, arg8)
+# define DEFINEFUNC8(ret, func, arg1, a, arg2, b, arg3, c, arg4, d, arg5, e, arg6, f, arg7, g, arg8, h, err, funcret) \
+ typedef ret (*_q_PTR_##func)(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \
+ static _q_PTR_##func _q_##func = 0; \
+ ret q_##func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) { \
+ if (Q_UNLIKELY(!_q_##func)) { \
+ qsslSocketUnresolvedSymbolWarning(#func); \
+ err; \
+ } \
+ funcret _q_##func(a, b, c, d, e, f, g, h); \
+ }
+
// 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); \
@@ -573,6 +585,17 @@ void q_SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
int q_EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx);
int q_EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey);
+
+ int q_EVP_CIPHER_CTX_key_length(const EVP_CIPHER_CTX *ctx);
+ int q_EVP_CIPHER_CTX_iv_length(const EVP_CIPHER_CTX *ctx);
+ int q_EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl);
+
+ const EVP_CIPHER *q_EVP_aes_128_cbc(void);
+ int q_EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *x, int padding);
+
+ int q_PKCS5_PBKDF2_HMAC(const char *pass, int passlen, const unsigned char *salt, int saltlen,
+ int iter, const EVP_MD *digest, int keylen, unsigned char *out);
+ const EVP_MD *q_EVP_sha256(void);
#endif
// Helper function
diff --git a/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp b/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
index 205d9c6..8615670 100644
--- a/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
+++ b/tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp
@@ -27,7 +27,9 @@
******************************************************************************/
#include <QtCore/qdebug.h>
+#include <QtCore/qloggingcategory.h>
#include <QtKnx/qknxcurve25519.h>
+#include <QtKnx/qknxnetipsessionresponse.h>
#include <QtKnx/private/qknxcryptographicdata_p.h>
#include <QtTest/qtest.h>
@@ -46,6 +48,11 @@ class tst_qknxcryptographicengine : public QObject
Q_OBJECT
private slots:
+ void initTestCase()
+ {
+ QLoggingCategory::setFilterRules("qt.network.ssl=false");
+ }
+
void testPublicKey()
{
QKnxCurve25519PublicKey key;
@@ -140,6 +147,52 @@ private slots:
QCOMPARE(secret, QKnxByteArray::fromHex("d801525217618f0da90a4ff22148aee0"
"ff4c19b430e8081223ffe99c81a98b05"));
}
+
+ void testSessionKey()
+ {
+ auto sharedSecret = QKnxByteArray::fromHex("d801525217618f0da90a4ff22148aee0"
+ "ff4c19b430e8081223ffe99c81a98b05");
+ auto result = QKnxCryptographicEngine::sessionKey(sharedSecret);
+ QCOMPARE(result, QKnxByteArray::fromHex("289426c2912535ba98279a4d1843c487"));
+ }
+
+ void PKCS5_PBKDF2_HMAC()
+ {
+ // session authenticate
+ auto result = QKnxCryptographicEngine::userPasswordHash({ "secret" });
+ QCOMPARE(result, QKnxByteArray::fromHex("03fcedb66660251ec81a1a716901696a"));
+
+ // session response
+ result = QKnxCryptographicEngine::deviceAuthenticationCodeHash({ "trustme" });
+ QCOMPARE(result, QKnxByteArray::fromHex("e158e4012047bd6cc41aafbc5c04c1fc"));
+ }
+
+ void testMessageAuthenticationCode()
+ {
+ if (QKnxOpenSsl::sslLibraryVersionNumber() < 0x1010000fL)
+ return;
+
+ auto clientBytes = QKnxByteArray::fromHex("0aa227b4fd7a32319ba9960ac036ce0e"
+ "5c4507b5ae55161f1078b1dcfb3cb631");
+ auto client = QKnxCurve25519PublicKey::fromBytes(clientBytes);
+
+ auto serverBytes = QKnxByteArray::fromHex("bdf099909923143ef0a5de0b3be3687b"
+ "c5bd3cf5f9e6f901699cd870ec1ff824");
+ auto server = QKnxCurve25519PublicKey::fromBytes(serverBytes);
+
+ auto authCode = QKnxCryptographicEngine::messageAuthenticationCode(
+ QKnxCryptographicEngine::Mode::Encrypt, 0x0001,
+ QKnxNetIpFrameHeader { QKnxNetIp::ServiceType::SessionResponse, 0x32 },
+ client,
+ server,
+ QKnxByteArray::fromHex("e158e4012047bd6cc41aafbc5c04c1fc")
+ );
+
+ QCOMPARE(authCode, QKnxByteArray::fromHex("a922505aaa436163570bd5494c2df2a3"));
+ }
+
+ void cleanupTestCase()
+ {}
};