diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2018-08-20 16:21:23 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2018-08-22 13:35:16 +0000 |
commit | 24015b247b4b32db227cd2070d90dafa5d71d556 (patch) | |
tree | cd899a75f902299d168c90c712fe31ded2e1add6 | |
parent | 081ed0ba7c9a96a544d394611854f38660c88062 (diff) |
Fix KNX cryptographic engine implementation
Change-Id: Iffff0e4157467a38f842a87001f4216f4c87d17a
Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
-rw-r--r-- | src/knx/ssl/qknxcurve25519.cpp | 188 | ||||
-rw-r--r-- | src/knx/ssl/qknxcurve25519.h | 30 | ||||
-rw-r--r-- | src/knx/ssl/qsslsocket_openssl_symbols.cpp | 21 | ||||
-rw-r--r-- | src/knx/ssl/qsslsocket_openssl_symbols_p.h | 23 | ||||
-rw-r--r-- | tests/auto/qknxcryptographicengine/tst_qknxcryptographicengine.cpp | 53 |
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() + {} }; |