[libraries/qca] /: qca-ossl: do not dereference a DH key that failed to generate

Nekto Oleg <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit f2032d5dd5e7470d02ceeca581044b8b99322755 by Nekto Oleg.
Committed on 02/08/2026 at 19:04.
Pushed by aacid into branch 'master'.

qca-ossl: do not dereference a DH key that failed to generate

DHKeyMaker::run() leaves its result null when DH_generate_key() fails.
km_finished() then skips key setup but still reports completion, so
deriveKey() receives a context whose EVP_PKEY is null and hands it to
DH_size(). Check both keys and return an empty SymmetricKey instead,
matching what the function already does when DH_compute_key() fails.

testDH() could not have caught this: its guard required DH to appear in
supportedIOTypes(), which never happens because DH keys are not
serialisable, so the test skipped on every run. Drop that condition - the
test only generates keys - and add coverage for deriveKey() itself, which
was not exercised anywhere.

CCBUG: 482819

M  +10   -2    plugins/qca-ossl/qca-ossl.cpp
M  +31   -2    unittest/keygenunittest/keygenunittest.cpp

https://invent.kde.org/libraries/qca/-/commit/f2032d5dd5e7470d02ceeca581044b8b99322755

diff --git a/plugins/qca-ossl/qca-ossl.cpp b/plugins/qca-ossl/qca-ossl.cpp
index becfcc31..b20854c4 100644
--- a/plugins/qca-ossl/qca-ossl.cpp
+++ b/plugins/qca-ossl/qca-ossl.cpp
@@ -2721,8 +2721,16 @@ public:
 
     SymmetricKey deriveKey(const PKeyBase &theirs) override
     {
-        const DH     *dh   = EVP_PKEY_get0_DH(evp.pkey);
-        const DH     *them = EVP_PKEY_get0_DH(static_cast<const DHKey *>(&theirs)->evp.pkey);
+        // Not const: OpenSSL 1.1 takes a non-const EVP_PKEY * here.
+        EVP_PKEY *theirPkey = static_cast<const DHKey *>(&theirs)->evp.pkey;
+        if (!evp.pkey || !theirPkey)
+            return SymmetricKey();
+
+        const DH *dh   = EVP_PKEY_get0_DH(evp.pkey);
+        const DH *them = EVP_PKEY_get0_DH(theirPkey);
+        if (!dh || !them)
+            return SymmetricKey();
+
         const BIGNUM *bnpub_key;
         DH_get0_key(them, &bnpub_key, nullptr);
 
diff --git a/unittest/keygenunittest/keygenunittest.cpp b/unittest/keygenunittest/keygenunittest.cpp
index 385e0e06..8bf2c067 100644
--- a/unittest/keygenunittest/keygenunittest.cpp
+++ b/unittest/keygenunittest/keygenunittest.cpp
@@ -40,6 +40,7 @@ private Q_SLOTS:
     void testRSA();
     void testDSA();
     void testDH();
+    void testDHDeriveKey();
 
 private:
     QCA::Initializer *m_init;
@@ -129,8 +130,10 @@ void KeyGenUnitTest::testDH()
     QCOMPARE(keygen.isBusy(), false);
     QCOMPARE(keygen.blockingEnabled(), true);
 
-    if (!QCA::isSupported("pkey") || !QCA::PKey::supportedTypes().contains(QCA::PKey::DH) ||
-        !QCA::PKey::supportedIOTypes().contains(QCA::PKey::DH))
+    // Only key generation is exercised here, so do not require DH to appear in
+    // supportedIOTypes() - DH keys are not serialisable, which made this test
+    // skip unconditionally.
+    if (!QCA::isSupported("pkey") || !QCA::PKey::supportedTypes().contains(QCA::PKey::DH))
         QSKIP("DH not supported!");
 
     QCA::DLGroup      group = keygen.createDLGroup(QCA::IETF_1024);
@@ -146,6 +149,32 @@ void KeyGenUnitTest::testDH()
     QCOMPARE(dh1.bitSize(), 2048);
 }
 
+void KeyGenUnitTest::testDHDeriveKey()
+{
+    QCA::KeyGenerator keygen;
+
+    if (!QCA::isSupported("pkey") || !QCA::PKey::supportedTypes().contains(QCA::PKey::DH))
+        QSKIP("DH not supported!");
+
+    const QCA::DLGroup group = keygen.createDLGroup(QCA::IETF_1024);
+    QCOMPARE(group.isNull(), false);
+
+    QCA::PrivateKey ours   = keygen.createDH(group);
+    QCA::PrivateKey theirs = keygen.createDH(group);
+    QCOMPARE(ours.isNull(), false);
+    QCOMPARE(theirs.isNull(), false);
+
+    // Key agreement is what consumers such as the Secret Service DH-AES session
+    // actually use. A key that failed to generate reaches this call looking
+    // valid, so exercise it rather than only inspecting the generated keys.
+    const QCA::SymmetricKey shared = ours.deriveKey(theirs.toPublicKey());
+    QCOMPARE(shared.isEmpty(), false);
+
+    // Both sides must arrive at the same secret.
+    const QCA::SymmetricKey otherSide = theirs.deriveKey(ours.toPublicKey());
+    QCOMPARE(shared, otherSide);
+}
+
 QTEST_MAIN(KeyGenUnitTest)
 
 #include "keygenunittest.moc"
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.