Commit 2fd42c70b7 for openssl.org

commit 2fd42c70b7f02e952dfd2e7f00bbbb543c52d67f
Author: Viktor Dukhovni <viktor@openssl.org>
Date:   Wed May 27 21:51:56 2026 +1000

    Expand and refactor test_evp_extra2

    The "expand" part adds a algorithm-independent probe that walks every
    gettable OSSL_PARAM of an EVP_PKEY in two passes -- first a size query,
    then a real fetch.  This is exercised for each algorithm.  In the SM2
    case it catches the gettable-params/getter type mismatch fixed in the
    preceding commit: reverting that commit trips the probe for SM2.

    The "refactor" part replaces the per-algorithm test code with a single
    PKEY_TEST_SPEC table of (name, optional prefab DER, optional keygen
    params), driven through pkey_from_spec().  The key-selection and
    gettable tests no longer involve any algorithm-specific code.

    Reviewed-by: Tim Hudson <tjh@openssl.org>
    Reviewed-by: Andrew Dinh <andrewd@openssl.org>
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    MergeDate: Sat Aug  8 13:21:30 2026
    (Merged from https://github.com/openssl/openssl/pull/30938)

diff --git a/test/evp_extra_test2.c b/test/evp_extra_test2.c
index c9b64bbf26..5eb3fe79d5 100644
--- a/test/evp_extra_test2.c
+++ b/test/evp_extra_test2.c
@@ -178,6 +178,115 @@ static const unsigned char kExampleRSAKeyPKCS8[] = {
     0x20, 0x1b, 0xe5, 0xdf
 };

+/*
+ * An RSA-PSS 2048-bit private key in ASN.1 DER form.  Used by the
+ * table-driven checks instead of keygen to save CI cycles (RSA keygen
+ * at this size is slow).  Generated with:
+ * > openssl genpkey -quiet -algorithm rsa-pss -outform DER > rsa-pss.der
+ * > xxd -i < rsa-pss.der
+ * Of course, never use this key for anything but tests.
+ */
+static const unsigned char kExampleRSAPSSKeyDER[] = {
+    0x30, 0x82, 0x04, 0xba, 0x02, 0x01, 0x00, 0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
+    0xf7, 0x0d, 0x01, 0x01, 0x0a, 0x04, 0x82, 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01,
+    0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa3, 0x32, 0x24, 0xdb, 0x9b, 0xec, 0x8f, 0x18, 0xbe,
+    0xeb, 0xef, 0x9b, 0x0e, 0x06, 0x11, 0x78, 0x26, 0x66, 0x2b, 0x75, 0xff, 0x79, 0x50, 0xa9,
+    0x96, 0xb2, 0xdf, 0xe1, 0x71, 0x2f, 0x8f, 0x3f, 0x08, 0x16, 0x3f, 0x53, 0x5a, 0x2c, 0x7c,
+    0xbb, 0xb9, 0xa2, 0x54, 0x2c, 0x81, 0xaf, 0x58, 0xd1, 0xb4, 0xfe, 0xd6, 0x2a, 0x5c, 0x47,
+    0xfe, 0x20, 0xf0, 0xce, 0xdc, 0xfe, 0x08, 0x7f, 0x3a, 0x24, 0xcd, 0xa0, 0x87, 0xcb, 0x0e,
+    0xd7, 0xcf, 0xee, 0x26, 0x12, 0x1f, 0xdb, 0x8b, 0x0f, 0x12, 0x32, 0x05, 0x73, 0x8f, 0x60,
+    0xfa, 0x75, 0x49, 0xb5, 0x50, 0x2d, 0x45, 0x72, 0x8b, 0x1d, 0x7f, 0xd3, 0x47, 0x2c, 0x5b,
+    0x0e, 0x24, 0x28, 0xe2, 0xc6, 0x01, 0x74, 0x10, 0xbe, 0xe4, 0xc7, 0xd2, 0xe5, 0xf4, 0x90,
+    0x10, 0xc0, 0x28, 0x66, 0xed, 0xc4, 0x84, 0xac, 0xb2, 0x01, 0x8e, 0x8a, 0xd1, 0x86, 0x82,
+    0x77, 0x89, 0xd8, 0x64, 0xaa, 0xca, 0x1d, 0x27, 0x3e, 0xfd, 0x78, 0x4e, 0xd7, 0x8d, 0xc5,
+    0x25, 0xab, 0xb1, 0xfb, 0x94, 0x86, 0xa1, 0x3d, 0xc4, 0x8d, 0x62, 0x5e, 0x59, 0x13, 0x25,
+    0xb7, 0x77, 0x20, 0x7e, 0x0a, 0xf8, 0xd0, 0x4f, 0xf4, 0x9c, 0x6f, 0x19, 0x67, 0xe7, 0x6f,
+    0x62, 0xd9, 0xc9, 0xe5, 0x97, 0x90, 0xaa, 0xd5, 0x94, 0x98, 0x61, 0xb7, 0xf9, 0xfc, 0x86,
+    0x45, 0xd8, 0x6f, 0x37, 0x35, 0x51, 0x0d, 0x9f, 0xb0, 0x9f, 0xd5, 0x1b, 0x10, 0x87, 0xa2,
+    0xb9, 0xe0, 0x5e, 0xc9, 0x6b, 0x1b, 0x5d, 0xf3, 0x3a, 0x16, 0x4a, 0x79, 0x25, 0xcf, 0x95,
+    0xfc, 0xca, 0x8a, 0x7a, 0xac, 0x49, 0xd9, 0x36, 0xa5, 0x45, 0xc4, 0x9a, 0x01, 0x60, 0xed,
+    0x8b, 0xcf, 0x66, 0xf2, 0xfb, 0x98, 0x93, 0x69, 0x84, 0xf2, 0xa8, 0x02, 0x12, 0x4d, 0x7b,
+    0x48, 0xa1, 0xfb, 0x4d, 0x59, 0x96, 0x57, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01,
+    0x00, 0x0f, 0x43, 0x65, 0xda, 0x1a, 0x53, 0xbe, 0x30, 0x78, 0xce, 0xd5, 0x4a, 0xa1, 0xbd,
+    0xa4, 0x8d, 0xa1, 0xbd, 0xc5, 0x8b, 0xd4, 0x38, 0x92, 0xf8, 0x41, 0x8d, 0x7a, 0x4d, 0xfa,
+    0x09, 0x92, 0x32, 0xb9, 0x7d, 0x67, 0x2f, 0xd3, 0x0b, 0xbd, 0x20, 0xcb, 0xa4, 0x68, 0x8d,
+    0xff, 0x15, 0x18, 0xd0, 0x08, 0x47, 0x1b, 0x5d, 0xbb, 0xe2, 0xc9, 0xdc, 0x67, 0xd9, 0x59,
+    0x26, 0xf3, 0x41, 0x7e, 0xfd, 0x29, 0x34, 0x3d, 0xef, 0x1a, 0x74, 0xc9, 0xd0, 0x42, 0xc4,
+    0x48, 0x31, 0x7e, 0xe8, 0x8a, 0x71, 0x9e, 0x3b, 0xe8, 0x29, 0x90, 0x53, 0x3e, 0xf1, 0x13,
+    0xce, 0x20, 0x24, 0x2d, 0xe9, 0xb4, 0xe6, 0x5c, 0x4e, 0x44, 0x37, 0xe6, 0xde, 0xe2, 0x12,
+    0xd5, 0xd8, 0xf6, 0xa5, 0xfb, 0xfc, 0xca, 0x77, 0x52, 0x6f, 0xd4, 0xf9, 0x3b, 0xee, 0x20,
+    0x87, 0xd4, 0x46, 0xd8, 0xa1, 0x37, 0xa5, 0x39, 0x22, 0x5e, 0xb6, 0x2c, 0xe8, 0x49, 0xf4,
+    0x78, 0x07, 0x23, 0xba, 0x19, 0x6e, 0x25, 0xb6, 0x7b, 0x2b, 0x9d, 0x7e, 0x19, 0x26, 0x95,
+    0xe7, 0x20, 0xb8, 0x21, 0xd1, 0xf5, 0xb9, 0x6d, 0xa6, 0x2a, 0xcb, 0x46, 0xe4, 0x62, 0x71,
+    0x63, 0xe1, 0xa0, 0x77, 0x1d, 0xf7, 0x10, 0xf6, 0x1e, 0x70, 0x27, 0x72, 0xf8, 0x68, 0x19,
+    0x6d, 0x84, 0xa1, 0x46, 0xcd, 0x52, 0x0f, 0x86, 0x77, 0x7a, 0x92, 0x5c, 0xc6, 0x39, 0xc2,
+    0x67, 0x12, 0xb0, 0x68, 0x6c, 0xe0, 0x19, 0xde, 0x61, 0xeb, 0x39, 0x1f, 0x14, 0xd0, 0xc7,
+    0xd6, 0x77, 0xc2, 0x83, 0x9e, 0xe2, 0xa3, 0x3a, 0x22, 0x02, 0x45, 0xf1, 0x10, 0x86, 0x77,
+    0x4a, 0x95, 0xf3, 0x75, 0x90, 0x22, 0x9b, 0xb2, 0xcd, 0xcd, 0x2f, 0x27, 0xff, 0xe7, 0xac,
+    0x9c, 0xdb, 0x85, 0x31, 0xeb, 0x2b, 0xfa, 0x8f, 0x9e, 0xbe, 0x92, 0x1a, 0xcd, 0xd3, 0x98,
+    0x1a, 0x75, 0x02, 0x81, 0x81, 0x00, 0xd5, 0x06, 0xa1, 0x08, 0x41, 0x02, 0xab, 0x3a, 0x00,
+    0xf5, 0x74, 0x5f, 0xb7, 0xf0, 0xa7, 0x9d, 0x0a, 0x26, 0x32, 0x7e, 0x48, 0xa4, 0xf7, 0xbe,
+    0xcf, 0xba, 0x27, 0x2a, 0x7d, 0x60, 0xd2, 0x16, 0x59, 0xc3, 0xf0, 0x78, 0x33, 0x3a, 0x36,
+    0x86, 0xb5, 0xd4, 0x29, 0x25, 0x23, 0x5a, 0xeb, 0x5a, 0x96, 0x82, 0xfc, 0x08, 0x08, 0x1c,
+    0xcc, 0xed, 0xb3, 0xf1, 0x27, 0xca, 0x1c, 0xa8, 0xe8, 0x36, 0x77, 0xa4, 0x57, 0x9a, 0x1d,
+    0xde, 0x2b, 0x3b, 0xc3, 0x41, 0x7c, 0x58, 0x33, 0x11, 0x3b, 0xee, 0x7a, 0xc9, 0xe9, 0x4b,
+    0xa2, 0x3b, 0xc3, 0x68, 0x19, 0x6a, 0x00, 0x7c, 0x59, 0x90, 0x77, 0x11, 0xed, 0x47, 0x4a,
+    0x5a, 0xfe, 0x3a, 0x29, 0x3a, 0xad, 0x80, 0xe9, 0xff, 0xce, 0x07, 0x3f, 0x80, 0x5f, 0x98,
+    0x14, 0x1a, 0xf0, 0x9c, 0xc7, 0x8d, 0x42, 0x06, 0xc4, 0xf8, 0x89, 0xe3, 0xe0, 0x23, 0x02,
+    0x81, 0x81, 0x00, 0xc4, 0x1e, 0x20, 0x31, 0xfa, 0xe7, 0x6a, 0xa5, 0x18, 0xbe, 0xce, 0x45,
+    0x15, 0x97, 0x67, 0x5f, 0xd0, 0xc6, 0x0f, 0xa2, 0x6c, 0x68, 0x5e, 0xe9, 0xca, 0x48, 0x8c,
+    0x46, 0x08, 0x06, 0xc1, 0xe9, 0xac, 0x8c, 0x01, 0x55, 0xc7, 0xf2, 0x02, 0x98, 0x2b, 0x8d,
+    0x4e, 0x24, 0x2d, 0x56, 0x7b, 0x40, 0x24, 0x28, 0xaa, 0x4a, 0xf7, 0x83, 0x88, 0x38, 0xa5,
+    0x03, 0x2b, 0xb3, 0x1c, 0x4a, 0x95, 0x70, 0xf6, 0x80, 0xcc, 0x21, 0xd0, 0xab, 0x8a, 0x10,
+    0xb2, 0xa6, 0x1a, 0x27, 0xb6, 0xae, 0x24, 0xed, 0xa9, 0xfe, 0xfc, 0x74, 0x7f, 0x97, 0x9b,
+    0xe1, 0xc2, 0x46, 0x48, 0xed, 0x5f, 0x2f, 0x9e, 0x54, 0x77, 0x1f, 0x61, 0x46, 0x24, 0x08,
+    0x7e, 0xb5, 0x63, 0xa1, 0xe2, 0x6f, 0x58, 0xc8, 0x76, 0x98, 0x8a, 0x9a, 0xa5, 0xb9, 0x15,
+    0xfb, 0xf4, 0xb8, 0xfd, 0x84, 0xb6, 0xbc, 0x5f, 0x59, 0xfa, 0x3d, 0x02, 0x81, 0x80, 0x2a,
+    0x97, 0x43, 0xb4, 0xf1, 0xc2, 0x85, 0xd7, 0x77, 0xff, 0x9b, 0x3f, 0xd3, 0xf7, 0xe2, 0x9b,
+    0x2c, 0x3f, 0x4c, 0xd3, 0x20, 0xd5, 0x12, 0xcf, 0x6c, 0x9a, 0xcc, 0x5f, 0xdb, 0x67, 0x73,
+    0x39, 0x92, 0xc2, 0xf1, 0x1e, 0x27, 0x27, 0xd9, 0x64, 0xff, 0x8f, 0xa6, 0x96, 0x4d, 0x46,
+    0x4d, 0x4d, 0xf4, 0x4f, 0xc5, 0xf2, 0x19, 0x25, 0x20, 0xf7, 0xd9, 0x85, 0x3a, 0xae, 0x57,
+    0x22, 0x92, 0x22, 0xe9, 0xb2, 0xca, 0xec, 0xfe, 0x51, 0xc0, 0x15, 0x17, 0xc9, 0xcd, 0x01,
+    0x99, 0x5c, 0x35, 0xbe, 0x4d, 0x80, 0x77, 0x0b, 0x6f, 0x29, 0x41, 0xbd, 0x5b, 0x6f, 0x6d,
+    0x1f, 0x6f, 0x8c, 0xc9, 0xb1, 0xa4, 0xde, 0x1f, 0x08, 0xa8, 0x51, 0x9a, 0x92, 0xa7, 0xc3,
+    0xe1, 0x85, 0x0a, 0x7d, 0x3a, 0x7e, 0x01, 0x1e, 0xd7, 0xce, 0x37, 0x13, 0x31, 0x76, 0x95,
+    0x9f, 0xe2, 0xdb, 0xca, 0x3d, 0xec, 0x3f, 0x02, 0x81, 0x80, 0x73, 0x99, 0x66, 0x54, 0xcf,
+    0x96, 0x34, 0xd0, 0x58, 0x03, 0xb0, 0x46, 0x1c, 0x72, 0x00, 0x27, 0x04, 0x42, 0x9a, 0xd8,
+    0x9c, 0x99, 0xf0, 0xc8, 0x51, 0xc9, 0xed, 0x95, 0x22, 0x0a, 0x09, 0xa7, 0x19, 0x63, 0xf5,
+    0x2d, 0x81, 0x0b, 0xef, 0xe7, 0x8e, 0x54, 0x5e, 0x69, 0x08, 0xa8, 0x5f, 0x41, 0xf1, 0x8e,
+    0x5e, 0xc2, 0x8d, 0x9e, 0xe4, 0x5e, 0xaf, 0x35, 0x6d, 0x3e, 0xc8, 0x40, 0x56, 0x52, 0x1d,
+    0x9a, 0xd1, 0xb8, 0x64, 0xed, 0x98, 0x16, 0x3b, 0x97, 0x98, 0xf7, 0x2b, 0xc0, 0xfe, 0x57,
+    0x70, 0xca, 0xb2, 0x38, 0x61, 0x35, 0x16, 0x57, 0x3e, 0x52, 0x7b, 0x8e, 0x1f, 0x7b, 0x4c,
+    0x12, 0x71, 0x9c, 0xf7, 0x93, 0x86, 0x8d, 0xd3, 0x52, 0x4c, 0x06, 0x12, 0x18, 0x9b, 0xf6,
+    0x9d, 0xa1, 0x50, 0xa1, 0xd3, 0x69, 0x83, 0xc1, 0xde, 0x00, 0x64, 0xbf, 0x23, 0x8f, 0x69,
+    0x8c, 0xf9, 0xe9, 0x02, 0x81, 0x80, 0x02, 0xce, 0x66, 0x6b, 0x32, 0x73, 0x62, 0x60, 0x27,
+    0x3e, 0x38, 0x1d, 0xd8, 0x70, 0xdb, 0x4e, 0x32, 0xf7, 0x7a, 0x7b, 0x22, 0x7b, 0x9b, 0xd7,
+    0x49, 0x4f, 0x5b, 0x9b, 0xaa, 0x2c, 0x5b, 0x99, 0x22, 0x1e, 0x6e, 0x7e, 0x19, 0x8a, 0xf1,
+    0x97, 0xfe, 0x3f, 0xcd, 0x9e, 0xa4, 0x46, 0xf0, 0x9a, 0x62, 0x0f, 0x1a, 0xcd, 0x77, 0xe1,
+    0x88, 0xe1, 0x9a, 0x22, 0x84, 0x1b, 0xbf, 0xf0, 0x71, 0xf4, 0x64, 0xfd, 0xd5, 0xaa, 0xb4,
+    0x28, 0xdc, 0xef, 0xd9, 0xec, 0x29, 0x11, 0xc4, 0x58, 0x0f, 0xeb, 0x3c, 0x41, 0x66, 0x4a,
+    0x98, 0x18, 0x26, 0xfe, 0x3e, 0x7e, 0xc3, 0x36, 0xfa, 0xbc, 0x64, 0x69, 0x1f, 0xce, 0x1c,
+    0xc1, 0xb5, 0xe9, 0xa8, 0x78, 0xf7, 0x6d, 0xb6, 0x0c, 0x58, 0x29, 0xe2, 0xbf, 0xd0, 0xbe,
+    0x2e, 0x10, 0xfa, 0x74, 0x02, 0x9b, 0x48, 0xa5, 0xc9, 0x74, 0xed, 0xed, 0x7d, 0xa8
+};
+
+#ifndef OPENSSL_NO_DH
+static char group_ffdhe2048[] = "ffdhe2048";
+static const OSSL_PARAM dhx_keygen_params[] = {
+    OSSL_PARAM_utf8_string("group", group_ffdhe2048,
+        sizeof(group_ffdhe2048) - 1),
+    OSSL_PARAM_END
+};
+#endif
+
+#ifndef OPENSSL_NO_EC
+static char group_p256[] = "P-256";
+static const OSSL_PARAM ec_keygen_params[] = {
+    OSSL_PARAM_utf8_string("group", group_p256, sizeof(group_p256) - 1),
+    OSSL_PARAM_END
+};
+#endif
+
 #ifndef OPENSSL_NO_DH
 static const unsigned char kExampleDHPrivateKeyDER[] = {
     0x30, 0x82, 0x02, 0x26, 0x02, 0x01, 0x00, 0x30, 0x82, 0x01, 0x17, 0x06,
@@ -296,49 +405,73 @@ static APK_DATA keydata[] = {
 static int pkey_has_private(EVP_PKEY *key, const char *privtag,
     int use_octstring)
 {
-    int ret = 0;
-
     if (use_octstring) {
-        unsigned char buf[64];
-
-        ret = EVP_PKEY_get_octet_string_param(key, privtag, buf, sizeof(buf),
-            NULL);
+        /*
+         * Existence probe only: pass NULL buffer, ignore the returned size.
+         * Avoids hard-coded fixed buffers that would cap at 64 bytes and
+         * fail for PQC private keys (ML-DSA-44 is ~2.5 KB).
+         */
+        return EVP_PKEY_get_octet_string_param(key, privtag, NULL, 0, NULL);
     } else {
         BIGNUM *bn = NULL;
+        int ret = EVP_PKEY_get_bn_param(key, privtag, &bn);

-        ret = EVP_PKEY_get_bn_param(key, privtag, &bn);
         BN_free(bn);
+        return ret;
     }
-    return ret;
 }

+/*
+ * Private-component tags we know about, in lookup order.  Extend this
+ * list if a new algorithm adopts a different convention; do_pkey_tofrom
+ * _data_select() scans it on each key and only needs one match.
+ */
+static const char *const pkey_priv_tags[] = {
+    OSSL_PKEY_PARAM_PRIV_KEY, /* DH, DSA, EC, ECX, SM2, ML-*, SLH-DSA */
+    OSSL_PKEY_PARAM_RSA_D, /* RSA, RSA-PSS */
+};
+
 static int do_pkey_tofrom_data_select(EVP_PKEY *key, const char *keytype)
 {
     int ret = 0;
     OSSL_PARAM *pub_params = NULL, *keypair_params = NULL;
+    const OSSL_PARAM *priv = NULL;
     EVP_PKEY *fromkey = NULL, *fromkeypair = NULL;
     EVP_PKEY_CTX *fromctx = NULL;
-    const char *privtag = strcmp(keytype, "RSA") == 0 ? "d" : "priv";
-    const int use_octstring = strcmp(keytype, "X25519") == 0;
+    const char *privtag;
+    int use_octstring;
+    size_t t;

     /*
-     * Select only the public key component when using EVP_PKEY_todata() and
-     * check that the resulting param array does not contain a private key.
+     * Export the full keypair first so we can discover the algorithm's
+     * private-component tag from the emitted params rather than hand-coding
+     * it per keytype.
      */
-    if (!TEST_int_eq(EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &pub_params), 1)
-        || !TEST_ptr_null(OSSL_PARAM_locate(pub_params, privtag)))
+    if (!TEST_int_eq(EVP_PKEY_todata(key, EVP_PKEY_KEYPAIR, &keypair_params), 1))
+        goto end;
+
+    for (t = 0; t < OSSL_NELEM(pkey_priv_tags); t++)
+        if ((priv = OSSL_PARAM_locate(keypair_params, pkey_priv_tags[t])) != NULL)
+            break;
+    if (priv == NULL) {
+        TEST_error("%s: KEYPAIR todata() emitted no known private component",
+            keytype);
         goto end;
+    }
+    privtag = priv->key;
+    use_octstring = priv->data_type == OSSL_PARAM_OCTET_STRING;
+
     /*
-     * Select the keypair when using EVP_PKEY_todata() and check that
-     * the param array contains a private key.
+     * Select only the public key via EVP_PKEY_todata(): the private tag we
+     * just learned must be absent.
      */
-    if (!TEST_int_eq(EVP_PKEY_todata(key, EVP_PKEY_KEYPAIR, &keypair_params), 1)
-        || !TEST_ptr(OSSL_PARAM_locate(keypair_params, privtag)))
+    if (!TEST_int_eq(EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &pub_params), 1)
+        || !TEST_ptr_null(OSSL_PARAM_locate(pub_params, privtag)))
         goto end;

     /*
-     * Select only the public key when using EVP_PKEY_fromdata() and check that
-     * the resulting key does not contain a private key.
+     * Round-trip: importing only the public selection from the full keypair
+     * params must yield a key that has no private component.
      */
     if (!TEST_ptr(fromctx = EVP_PKEY_CTX_new_from_name(mainctx, keytype, NULL))
         || !TEST_int_eq(EVP_PKEY_fromdata_init(fromctx), 1)
@@ -347,10 +480,8 @@ static int do_pkey_tofrom_data_select(EVP_PKEY *key, const char *keytype)
             1)
         || !TEST_false(pkey_has_private(fromkey, privtag, use_octstring)))
         goto end;
-    /*
-     * Select the keypair when using EVP_PKEY_fromdata() and check that
-     * the resulting key contains a private key.
-     */
+
+    /* Round-trip: a full keypair import must carry the private component. */
     if (!TEST_int_eq(EVP_PKEY_fromdata(fromctx, &fromkeypair,
                          EVP_PKEY_KEYPAIR, keypair_params),
             1)
@@ -366,35 +497,135 @@ end:
     return ret;
 }

-#ifndef OPENSSL_NO_DH
-static int test_dh_tofrom_data_select(void)
+/*
+ * Pass every advertised gettable parameter through EVP_PKEY_get_params()
+ * as a probe, twice: once with NULL data (size negotiation), then again
+ * with storage allocated for the advertised type.  Either fetch must
+ * succeed as a whole.  Pass 2 is what exercises the type-matching
+ * branches in the per-type OSSL_PARAM setters: a NULL-data probe
+ * succeeds before those branches are reached, which is why this check
+ * would otherwise miss gettable-table / getter mismatches (an entry
+ * advertised as OCTET_STRING whose getter populates it as a BIGNUM).
+ *
+ * The provider is free to leave individual entries unmodified, e.g.
+ * private-key data on a public-only key; only whole-fetch failure
+ * is treated as a regression.
+ */
+static int do_pkey_all_gettables_probe(EVP_PKEY *pkey, const char *keytype)
 {
-    int ret;
-    OSSL_PARAM params[2];
-    EVP_PKEY *key = NULL;
-    EVP_PKEY_CTX *gctx = NULL;
-#ifndef OPENSSL_NO_DEPRECATED_3_0
-    const DH *dhkey;
-    const BIGNUM *privkey;
-#endif
+    int ret = 0;
+    const OSSL_PARAM *gettable;
+    OSSL_PARAM *probe = NULL;
+    void **storage = NULL;
+    size_t n = 0, i;
+    int need_refetch = 0;

-    params[0] = OSSL_PARAM_construct_utf8_string("group", "ffdhe2048", 0);
-    params[1] = OSSL_PARAM_construct_end();
-    ret = TEST_ptr(gctx = EVP_PKEY_CTX_new_from_name(mainctx, "DHX", NULL))
-        && TEST_int_gt(EVP_PKEY_keygen_init(gctx), 0)
-        && TEST_true(EVP_PKEY_CTX_set_params(gctx, params))
-        && TEST_int_gt(EVP_PKEY_generate(gctx, &key), 0)
-        && TEST_true(do_pkey_tofrom_data_select(key, "DHX"));
-#ifndef OPENSSL_NO_DEPRECATED_3_0
-    ret = ret && TEST_ptr(dhkey = EVP_PKEY_get0_DH(key))
-        && TEST_ptr(privkey = DH_get0_priv_key(dhkey))
-        && TEST_int_le(BN_num_bits(privkey), 225);
-#endif
-    EVP_PKEY_free(key);
-    EVP_PKEY_CTX_free(gctx);
+    if (!TEST_ptr(gettable = EVP_PKEY_gettable_params(pkey)))
+        goto err;
+
+    while (gettable[n].key != NULL)
+        n++;
+    if (!TEST_ptr(probe = OPENSSL_zalloc((n + 1) * sizeof(*probe)))
+        || (n > 0
+            && !TEST_ptr(storage = OPENSSL_zalloc(n * sizeof(*storage)))))
+        goto err;
+
+    for (i = 0; i < n; i++) {
+        /*
+         * Copy the advertised entry as a template, then reset the
+         * data fields to the "probe" state.  For OCTET_PTR / UTF8_PTR
+         * the setter writes *data directly (no size negotiation),
+         * so hand it a pointer slot up front.
+         */
+        probe[i] = gettable[i];
+        probe[i].data = NULL;
+        probe[i].data_size = 0;
+        probe[i].return_size = OSSL_PARAM_UNMODIFIED;
+        if (gettable[i].data_type == OSSL_PARAM_OCTET_PTR
+            || gettable[i].data_type == OSSL_PARAM_UTF8_PTR) {
+            if (!TEST_ptr(storage[i] = OPENSSL_zalloc(sizeof(void *))))
+                goto err;
+            probe[i].data = storage[i];
+        }
+    }
+    probe[n] = OSSL_PARAM_construct_end();
+
+    /* Pass 1: NULL-data probe populates return_size for sized entries. */
+    if (!TEST_true(EVP_PKEY_get_params(pkey, probe))) {
+        TEST_info("%s: Pass 1 (size probe) failed", keytype);
+        goto err;
+    }
+
+    /* Allocate storage for each populated sized entry. */
+    for (i = 0; i < n; i++) {
+        size_t need;
+
+        if (!OSSL_PARAM_modified(&probe[i]))
+            continue;
+        switch (probe[i].data_type) {
+        case OSSL_PARAM_INTEGER:
+        case OSSL_PARAM_UNSIGNED_INTEGER:
+        case OSSL_PARAM_OCTET_STRING:
+        case OSSL_PARAM_REAL:
+            need = probe[i].return_size;
+            if (!TEST_ptr(storage[i] = OPENSSL_malloc(need == 0 ? 1 : need)))
+                goto err;
+            probe[i].data = storage[i];
+            probe[i].data_size = need;
+            probe[i].return_size = OSSL_PARAM_UNMODIFIED;
+            need_refetch = 1;
+            break;
+        case OSSL_PARAM_UTF8_STRING:
+            /* Leave room for a trailing NUL beyond return_size. */
+            need = probe[i].return_size + 1;
+            if (!TEST_ptr(storage[i] = OPENSSL_malloc(need)))
+                goto err;
+            probe[i].data = storage[i];
+            probe[i].data_size = need;
+            probe[i].return_size = OSSL_PARAM_UNMODIFIED;
+            need_refetch = 1;
+            break;
+        default:
+            /* PTR types were populated in Pass 1; unknown types left as-is. */
+            break;
+        }
+    }
+
+    /*
+     * Pass 2: real fetch, with typed storage.  This is what actually
+     * catches advertised-vs-populated type mismatches: the per-type
+     * OSSL_PARAM_set_*() setters only enforce data_type when data is
+     * non-NULL.
+     */
+    if (need_refetch && !TEST_true(EVP_PKEY_get_params(pkey, probe))) {
+        TEST_info("%s: Pass 2 (real fetch) failed", keytype);
+        goto err;
+    }
+    ret = 1;
+err:
+    if (storage != NULL)
+        for (i = 0; i < n; i++)
+            OPENSSL_free(storage[i]);
+    OPENSSL_free(storage);
+    OPENSSL_free(probe);
     return ret;
 }

+/*
+ * Composite check: exercise both the selection-mask semantics of
+ * todata/fromdata and the advertised-vs-populated type consistency of
+ * every gettable parameter on the given key.  The individual helpers are
+ * algorithm-agnostic, so this driver works for any asymmetric EVP_PKEY.
+ * Each keytype-specific test function reduces to (a) key generation and
+ * (b) invoking this driver.
+ */
+static int do_pkey_checks(EVP_PKEY *key, const char *keytype)
+{
+    return do_pkey_tofrom_data_select(key, keytype)
+        && do_pkey_all_gettables_probe(key, keytype);
+}
+
+#ifndef OPENSSL_NO_DH
 static int test_dh_paramgen(void)
 {
     int ret;
@@ -559,57 +790,7 @@ err:
     return ret;
 }

-static int test_ec_tofrom_data_select(void)
-{
-    int ret;
-    EVP_PKEY *key = NULL;
-
-    ret = TEST_ptr(key = EVP_PKEY_Q_keygen(mainctx, NULL, "EC", "P-256"))
-        && TEST_true(do_pkey_tofrom_data_select(key, "EC"));
-    EVP_PKEY_free(key);
-    return ret;
-}
-
-#ifndef OPENSSL_NO_ECX
-static int test_ecx_tofrom_data_select(void)
-{
-    int ret;
-    EVP_PKEY *key = NULL;
-
-    ret = TEST_ptr(key = EVP_PKEY_Q_keygen(mainctx, NULL, "X25519"))
-        && TEST_true(do_pkey_tofrom_data_select(key, "X25519"));
-    EVP_PKEY_free(key);
-    return ret;
-}
-#endif
-#endif
-
-#ifndef OPENSSL_NO_SM2
-static int test_sm2_tofrom_data_select(void)
-{
-    int ret;
-    EVP_PKEY *key = NULL;
-
-    ret = TEST_ptr(key = EVP_PKEY_Q_keygen(mainctx, NULL, "SM2"))
-        && TEST_true(do_pkey_tofrom_data_select(key, "SM2"));
-    EVP_PKEY_free(key);
-    return ret;
-}
-#endif
-
-static int test_rsa_tofrom_data_select(void)
-{
-    int ret;
-    EVP_PKEY *key = NULL;
-    const unsigned char *pdata = kExampleRSAKeyDER;
-    int pdata_len = sizeof(kExampleRSAKeyDER);
-
-    ret = TEST_ptr(key = d2i_AutoPrivateKey_ex(NULL, &pdata, pdata_len,
-                       mainctx, NULL))
-        && TEST_true(do_pkey_tofrom_data_select(key, "RSA"));
-    EVP_PKEY_free(key);
-    return ret;
-}
+#endif /* OPENSSL_NO_EC */

 /* This is the equivalent of test_d2i_AutoPrivateKey in evp_extra_test */
 static int test_d2i_AutoPrivateKey_ex(int i)
@@ -1070,7 +1251,9 @@ static const unsigned char dsa_pub[] = {
     0xcc, 0xe2, 0x46, 0xce, 0xf5, 0x6d, 0xd8, 0x18, 0x91, 0xc4, 0x20, 0xbf, 0x07, 0x48, 0x45,
     0xfd
 };
+#endif

+#ifndef OPENSSL_NO_DSA
 static int do_check_params(OSSL_PARAM key_params[], int expected)
 {
     EVP_PKEY_CTX *gen_ctx = NULL, *check_ctx = NULL;
@@ -1117,20 +1300,6 @@ static int do_check_int(OSSL_PARAM params[], const char *key, int expected)
         && TEST_int_eq(val, expected);
 }

-static int test_dsa_tofrom_data_select(void)
-{
-    int ret;
-    EVP_PKEY *key = NULL;
-    const unsigned char *pkeydata = dsa_key;
-
-    ret = TEST_ptr(key = d2i_AutoPrivateKey_ex(NULL, &pkeydata, sizeof(dsa_key),
-                       mainctx, NULL))
-        && TEST_true(do_pkey_tofrom_data_select(key, "DSA"));
-
-    EVP_PKEY_free(key);
-    return ret;
-}
-
 static int test_dsa_todata(void)
 {
     EVP_PKEY *pkey = NULL;
@@ -1554,6 +1723,112 @@ static int evp_test_name_parsing(void)
     return 1;
 }

+/*
+ * Table-driven driver for do_pkey_checks().  Each entry names the
+ * algorithm and supplies either (a) a prefabricated DER blob, loaded
+ * via d2i_AutoPrivateKey_ex(), or (b) an optional OSSL_PARAM array
+ * passed to EVP_PKEY_CTX_set_params() before EVP_PKEY_generate().
+ * The downstream checks are algorithm-agnostic; only the per-algorithm
+ * inputs live in this table.
+ */
+typedef struct pkey_test_spec_st {
+    const char *name; /* algorithm name for keygen or logging */
+    const unsigned char *der; /* prefab private key DER, or NULL */
+    uint32_t der_len; /* size of the DER blob when set */
+    const OSSL_PARAM *params; /* keygen params, or NULL for defaults */
+} PKEY_TEST_SPEC;
+
+static const PKEY_TEST_SPEC pkey_test_specs[] = {
+#ifndef OPENSSL_NO_DH
+    { "DHX", NULL, 0, dhx_keygen_params },
+#endif
+#ifndef OPENSSL_NO_DSA
+    { "DSA", dsa_key, sizeof(dsa_key), NULL },
+#endif
+    { "RSA", kExampleRSAKeyDER, sizeof(kExampleRSAKeyDER), NULL },
+    { "RSA-PSS", kExampleRSAPSSKeyDER, sizeof(kExampleRSAPSSKeyDER), NULL },
+#ifndef OPENSSL_NO_EC
+    { "EC", NULL, 0, ec_keygen_params },
+#ifndef OPENSSL_NO_ECX
+    { "X25519", NULL, 0, NULL },
+    { "ED25519", NULL, 0, NULL },
+#endif
+#ifndef OPENSSL_NO_SM2
+    { "SM2", NULL, 0, NULL },
+#endif
+#endif
+#ifndef OPENSSL_NO_ML_KEM
+    { "ML-KEM-512", NULL, 0, NULL },
+#endif
+#ifndef OPENSSL_NO_ML_DSA
+    { "ML-DSA-44", NULL, 0, NULL },
+#endif
+#ifndef OPENSSL_NO_SLH_DSA
+    { "SLH-DSA-SHA2-128f", NULL, 0, NULL },
+#endif
+};
+
+static EVP_PKEY *pkey_from_spec(OSSL_LIB_CTX *libctx,
+    const PKEY_TEST_SPEC *spec)
+{
+    EVP_PKEY *key = NULL;
+    EVP_PKEY_CTX *gctx = NULL;
+
+    if (spec->der != NULL) {
+        const unsigned char *p = spec->der;
+
+        return d2i_AutoPrivateKey_ex(NULL, &p, spec->der_len, libctx, NULL);
+    }
+
+    if ((gctx = EVP_PKEY_CTX_new_from_name(libctx, spec->name, NULL)) == NULL
+        || EVP_PKEY_keygen_init(gctx) <= 0
+        || (spec->params != NULL
+            && EVP_PKEY_CTX_set_params(gctx, spec->params) <= 0)
+        || EVP_PKEY_generate(gctx, &key) <= 0) {
+        EVP_PKEY_free(key);
+        key = NULL;
+    }
+    EVP_PKEY_CTX_free(gctx);
+    return key;
+}
+
+static int test_pkey_by_spec(int i)
+{
+    EVP_PKEY *key = pkey_from_spec(mainctx, &pkey_test_specs[i]);
+    int ret = TEST_ptr(key)
+        && do_pkey_checks(key, pkey_test_specs[i].name);
+
+    EVP_PKEY_free(key);
+    return ret;
+}
+
+#if !defined(OPENSSL_NO_DH) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+/*
+ * DHX keygen with group "ffdhe2048" reduces the private exponent modulo
+ * the sub-group order, so the exposed BN_num_bits() should not exceed
+ * the sub-group size.  This tail check is DHX-specific and therefore
+ * does not fit the generic spec-table driver.
+ */
+static int test_dh_priv_bits(void)
+{
+    static const PKEY_TEST_SPEC dhx_spec = {
+        "DHX", NULL, 0, dhx_keygen_params
+    };
+    int ret;
+    EVP_PKEY *key;
+    const DH *dhkey;
+    const BIGNUM *privkey;
+
+    if (!TEST_ptr(key = pkey_from_spec(mainctx, &dhx_spec)))
+        return 0;
+    ret = TEST_ptr(dhkey = EVP_PKEY_get0_DH(key))
+        && TEST_ptr(privkey = DH_get0_priv_key(dhkey))
+        && TEST_int_le(BN_num_bits(privkey), 225);
+    EVP_PKEY_free(key);
+    return ret;
+}
+#endif
+
 int setup_tests(void)
 {
     if (!test_get_libctx(&mainctx, &nullprov, NULL, NULL, NULL)) {
@@ -1568,28 +1843,22 @@ int setup_tests(void)
     ADD_TEST(test_new_keytype);
 #ifndef OPENSSL_NO_EC
     ADD_ALL_TESTS(test_d2i_PrivateKey_ex, 2);
-    ADD_TEST(test_ec_tofrom_data_select);
-#ifndef OPENSSL_NO_ECX
-    ADD_TEST(test_ecx_tofrom_data_select);
-#endif
     ADD_TEST(test_ec_d2i_i2d_pubkey);
 #else
     ADD_ALL_TESTS(test_d2i_PrivateKey_ex, 1);
 #endif
-#ifndef OPENSSL_NO_SM2
-    ADD_TEST(test_sm2_tofrom_data_select);
-#endif
 #ifndef OPENSSL_NO_DSA
     ADD_TEST(test_dsa_todata);
-    ADD_TEST(test_dsa_tofrom_data_select);
     ADD_ALL_TESTS(test_dsa_fromdata_digest_prop, 2);
 #endif
 #ifndef OPENSSL_NO_DH
-    ADD_TEST(test_dh_tofrom_data_select);
     ADD_TEST(test_dh_paramgen);
     ADD_TEST(test_dh_paramfromdata);
+#ifndef OPENSSL_NO_DEPRECATED_3_0
+    ADD_TEST(test_dh_priv_bits);
+#endif
 #endif
-    ADD_TEST(test_rsa_tofrom_data_select);
+    ADD_ALL_TESTS(test_pkey_by_spec, OSSL_NELEM(pkey_test_specs));

     ADD_TEST(test_pkey_todata_null);
     ADD_TEST(test_pkey_export_null);