/****************************************************************************** ** ** Copyright (C) 2018 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 "qknxsecurekey.h" #include "qknxcryptographicengine.h" #include #if QT_CONFIG(opensslv11) # include "private/qsslsocket_openssl_symbols_p.h" # include "private/qsslsocket_openssl11_symbols_p.h" #endif QT_BEGIN_NAMESPACE class QKnxSecureKeyData : public QSharedData { public: QKnxSecureKeyData() = default; ~QKnxSecureKeyData() { #if QT_CONFIG(opensslv11) if (m_evpPKey) QKnxPrivate::q_EVP_PKEY_free(m_evpPKey); #endif } bool isTypeValid() const { return m_type >= QKnxSecureKey::Type::Private && m_type < QKnxSecureKey::Type::Invalid; } #if QT_CONFIG(opensslv11) EVP_PKEY *m_evpPKey { nullptr }; #endif QKnxSecureKey::Type m_type { QKnxSecureKey::Type::Invalid }; }; /*! \since 5.13 \inmodule QtKnx \class QKnxSecureKey \ingroup qtknx-general-classes \brief The QKnxSecureKey class represents the elliptic-curve secure key to be used with the elliptic curve Diffie-Hellman (ECDH) key agreement scheme. 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 QKnxSecureKey::Type Describes the types of keys QKnxSecureKey supports. \value Private A private key. \value Public A public key. \value Invalid An invalid key, please do not use. */ /*! Constructs an empty invalid secure key. */ QKnxSecureKey::QKnxSecureKey() : d_ptr(new QKnxSecureKeyData) {} /*! Destroys the secure key and releases all allocated resources. */ QKnxSecureKey::~QKnxSecureKey() = default; /*! Returns the type of the key: \l {QKnxSecureKey::Public}{Public} or \l {QKnxSecureKey::Private}{Private}. */ QKnxSecureKey::Type QKnxSecureKey::type() const { return d_ptr->m_type; } /*! Returns \c true if this is a default constructed secure key; otherwise returns \c false. A secure key is considered \c null if it contains no initialized values. */ bool QKnxSecureKey::isNull() const { #if QT_CONFIG(opensslv11) return d_ptr->m_evpPKey == nullptr; #else return true; #endif } /*! Returns \c true if OpenSSL is available and if the key contains initialized values, otherwise returns \c false. */ bool QKnxSecureKey::isValid() const { #if QT_CONFIG(opensslv11) return !isNull() && d_ptr->isTypeValid() && QKnxCryptographicEngine::supportsCryptography(); #else return false; #endif } /*! Returns an array of bytes that represent the Curve25519 raw secure key. \note The function will return an empty byte array for the private key unless OpenSLL with version 1.1.1a is used as back-end. */ QKnxByteArray QKnxSecureKey::bytes() const { #if QT_CONFIG(opensslv11) if (!isValid()) return {}; if (d_ptr->m_type == Type::Private) { size_t len = 32; QKnxByteArray ba(int(len), 0); if (QKnxPrivate::q_EVP_PKEY_get_raw_private_key(d_ptr->m_evpPKey, ba.data(), &len) <= 0) return {}; // preferred, no other way possible return ba; } size_t len = 32; QKnxByteArray pub(32, Qt::Uninitialized); if (QKnxPrivate::q_EVP_PKEY_get_raw_public_key(d_ptr->m_evpPKey, pub.data(), &len) > 0) return pub; // preferred way pub.resize(QKnxPrivate::q_i2d_PUBKEY(d_ptr->m_evpPKey, nullptr)); auto tmp = pub.data(); QKnxPrivate::q_i2d_PUBKEY(d_ptr->m_evpPKey, &tmp); return pub.right(32); #else return {}; #endif } /*! Constructs the Curve25519 secure key from the byte array \a data starting at position \a index inside the array and sets the key type to \a type if OpenSSL is available and no error occurs; otherwise returns a \e {default-constructed key} which can be invalid. */ QKnxSecureKey QKnxSecureKey::fromBytes(QKnxSecureKey::Type type, const QKnxByteArray &data, quint16 index) { #if QT_CONFIG(opensslv11) auto ba = data.mid(index, 32); if (ba.size() < 32) return {}; if (!QKnxCryptographicEngine::supportsCryptography()) return {}; QKnxSecureKey key; key.d_ptr->m_type = type; if (type == Type::Private) { key.d_ptr->m_evpPKey = QKnxPrivate::q_EVP_PKEY_new_raw_private_key(NID_X25519, nullptr, ba.constData(), ba.size()); // preferred way if (key.d_ptr->m_evpPKey) return key; static const auto pkcs8 = QKnxByteArray::fromHex("302e020100300506032b656e04220420"); auto tmp = pkcs8 + ba; // PKCS #8 is a standard syntax for storing private key information BIO *bio = nullptr; if ((bio = QKnxPrivate::q_BIO_new_mem_buf(reinterpret_cast (tmp.data()), tmp.size()))) key.d_ptr->m_evpPKey = QKnxPrivate::q_d2i_PrivateKey_bio(bio, nullptr); QKnxPrivate::q_BIO_free(bio); return key; } if (type == Type::Public) { key.d_ptr->m_evpPKey = QKnxPrivate::q_EVP_PKEY_new_raw_public_key(NID_X25519, nullptr, ba.constData(), ba.size()); // preferred way if (key.d_ptr->m_evpPKey) return key; key.d_ptr->m_evpPKey = QKnxPrivate::q_EVP_PKEY_new(); if (QKnxPrivate::q_EVP_PKEY_set_type(key.d_ptr->m_evpPKey, NID_X25519) <= 0) return {}; if (QKnxPrivate::q_EVP_PKEY_set1_tls_encodedpoint(key.d_ptr->m_evpPKey, ba.constData(), ba.size()) <= 0) return {}; return key; } #else Q_UNUSED(type) Q_UNUSED(data) Q_UNUSED(index) #endif return {}; } /*! Returns a new valid private key if OpenSSL is available and no error occurs. */ QKnxSecureKey QKnxSecureKey::generatePrivateKey() { QKnxSecureKey key; #if QT_CONFIG(opensslv11) if (!QKnxCryptographicEngine::supportsCryptography()) return key; if (auto *pctx = QKnxPrivate::q_EVP_PKEY_CTX_new_id(NID_X25519, nullptr)) { QKnxPrivate::q_EVP_PKEY_keygen_init(pctx); key.d_ptr->m_type = Type::Private; QKnxPrivate::q_EVP_PKEY_keygen(pctx, &key.d_ptr->m_evpPKey); QKnxPrivate::q_EVP_PKEY_CTX_free(pctx); } #endif return key; } /*! Returns a new valid public key with the given private key \a privateKey if OpenSSL is available and no error occurs. */ QKnxSecureKey QKnxSecureKey::publicKeyFromPrivate(const QKnxSecureKey &privateKey) { QKnxSecureKey key; #if QT_CONFIG(opensslv11) if (privateKey.type() == QKnxSecureKey::Type::Private && privateKey.isValid()) { QKnxPrivate::q_EVP_PKEY_up_ref(privateKey.d_ptr->m_evpPKey); key.d_ptr->m_type = Type::Public; key.d_ptr->m_evpPKey = privateKey.d_ptr->m_evpPKey; } #else Q_UNUSED(privateKey) #endif return key; } /*! \overload publicKeyFromPrivate() */ QKnxSecureKey QKnxSecureKey::publicKeyFromPrivate(const QKnxByteArray &privateKey) { return QKnxSecureKey::publicKeyFromPrivate(QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, privateKey)); } /*! Sets \a privateKey to a new valid private key and \a publicKey to a new valid public key derived from the freshly generated private key if OpenSSL is available and no error occurs. */ void QKnxSecureKey::generateKeys(QKnxSecureKey *privateKey, QKnxSecureKey *publicKey) { if (!privateKey || !publicKey) return; *privateKey = generatePrivateKey(); *publicKey = publicKeyFromPrivate(*privateKey); } /*! Derives and returns the shared secret from the given private key \a privateKey and the peer's public key \a peerPublicKey if OpenSSL is available and no error occurs; otherwise returns a \l {default-constructed value} which can be empty. */ QKnxByteArray QKnxSecureKey::sharedSecret(const QKnxSecureKey &privateKey, const QKnxSecureKey &peerPublicKey) { #if QT_CONFIG(opensslv11) if (privateKey.type() != QKnxSecureKey::Type::Private || !privateKey.isValid()) return {}; if (peerPublicKey.type() != QKnxSecureKey::Type::Public || !peerPublicKey.isValid()) return {}; auto evpPKeyCtx = QKnxPrivate::q_EVP_PKEY_CTX_new(privateKey.d_ptr->m_evpPKey, nullptr); if (!evpPKeyCtx) return {}; struct ScopedFree final { ScopedFree(EVP_PKEY_CTX *key) : m_evpPKeyCtx(key) {} ~ScopedFree() { QKnxPrivate::q_EVP_PKEY_CTX_free(m_evpPKeyCtx); } EVP_PKEY_CTX *m_evpPKeyCtx = nullptr; } _{ evpPKeyCtx }; if (QKnxPrivate::q_EVP_PKEY_derive_init(evpPKeyCtx) <= 0) return {}; if (QKnxPrivate::q_EVP_PKEY_derive_set_peer(evpPKeyCtx, peerPublicKey.d_ptr->m_evpPKey) <= 0) return {}; size_t keylen = 0; if (QKnxPrivate::q_EVP_PKEY_derive(evpPKeyCtx, nullptr, &keylen) <= 0) return {}; QKnxByteArray ba(int(keylen), 0); if (QKnxPrivate::q_EVP_PKEY_derive(evpPKeyCtx, ba.data(), &keylen) <= 0) return {}; return ba; #else Q_UNUSED(privateKey) Q_UNUSED(peerPublicKey) return {}; #endif } /*! \overload sharedSecret() */ QKnxByteArray QKnxSecureKey::sharedSecret(const QKnxByteArray &privateKey, const QKnxByteArray &peerPublicKey) { return sharedSecret(QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Private, privateKey), QKnxSecureKey::fromBytes(QKnxSecureKey::Type::Public, peerPublicKey)); } /*! Constructs a copy of \a other. */ QKnxSecureKey::QKnxSecureKey(const QKnxSecureKey &other) : d_ptr(other.d_ptr) {} /*! Assigns the specified \a other to this object. */ QKnxSecureKey &QKnxSecureKey::operator=(const QKnxSecureKey &other) { d_ptr = other.d_ptr; return *this; } /*! Returns \c true if this key and the given \a other key are equal; otherwise returns \c false. */ bool QKnxSecureKey::operator==(const QKnxSecureKey &other) const { return d_ptr == other.d_ptr #if QT_CONFIG(opensslv11) || (d_ptr->m_evpPKey == other.d_ptr->m_evpPKey && d_ptr->m_type == other.d_ptr->m_type) #endif || (bytes() == other.bytes()); } /*! Returns \c true if this key and the given \a other key are not equal; otherwise returns \c false. */ bool QKnxSecureKey::operator!=(const QKnxSecureKey &other) const { return !operator==(other); } QT_END_NAMESPACE