Commit 1b035166bd for openssl.org

commit 1b035166bdb2d86e718ad51a55caffc8bc9504a7
Author: Helen Zhang <helzhang@cisco.com>
Date:   Fri Nov 21 19:11:26 2025 +0000

    Add SNMPKDF implementation

      In compliance with SP800-135 and RFC7860

    Reviewed-by: Shane Lontis <shane.lontis@oracle.com>
    Reviewed-by: Paul Dale <paul.dale@oracle.com>
    (Merged from https://github.com/openssl/openssl/pull/29195)

diff --git a/CHANGES.md b/CHANGES.md
index 562ee1112f..8a04fadc9c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -84,6 +84,10 @@ OpenSSL 4.0

    *Milan Broz*, *Neil Horman*, *Norbert Pocs*

+ * Added SNMP KDF (EVP_KDF_SNMPKDF) to EVP_KDF
+
+   *Barry Fussell and Helen Zhang*
+
 OpenSSL 3.6
 -----------

diff --git a/build.info b/build.info
index 1faec4d8de..f2d6da6c8b 100644
--- a/build.info
+++ b/build.info
@@ -67,6 +67,7 @@ DEPEND[]=include/openssl/asn1.h \
          providers/implementations/kdfs/pkcs12kdf.inc \
          providers/implementations/kdfs/pvkkdf.inc \
          providers/implementations/kdfs/scrypt.inc \
+         providers/implementations/kdfs/snmpkdf.inc \
          providers/implementations/kdfs/sshkdf.inc \
          providers/implementations/kdfs/sskdf.inc \
          providers/implementations/kdfs/tls1_prf.inc \
@@ -184,6 +185,7 @@ DEPEND[providers/implementations/asymciphers/rsa_enc.inc \
        providers/implementations/kdfs/pkcs12kdf.inc \
        providers/implementations/kdfs/pvkkdf.inc \
        providers/implementations/kdfs/scrypt.inc \
+       providers/implementations/kdfs/snmpkdf.inc \
        providers/implementations/kdfs/sshkdf.inc \
        providers/implementations/kdfs/sskdf.inc \
        providers/implementations/kdfs/tls1_prf.inc \
@@ -291,6 +293,8 @@ GENERATE[providers/implementations/kdfs/pvkkdf.inc]=\
     providers/implementations/kdfs/pvkkdf.inc.in
 GENERATE[providers/implementations/kdfs/scrypt.inc]=\
     providers/implementations/kdfs/scrypt.inc.in
+GENERATE[providers/implementations/kdfs/snmpkdf.inc]=\
+    providers/implementations/kdfs/snmpkdf.inc.in
 GENERATE[providers/implementations/kdfs/sshkdf.inc]=\
     providers/implementations/kdfs/sshkdf.inc.in
 GENERATE[providers/implementations/kdfs/sskdf.inc]=\
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index 568b6bd5aa..8bd83215dd 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -1101,6 +1101,7 @@ PROV_R_MISSING_CEK_ALG:144:missing cek alg
 PROV_R_MISSING_CIPHER:155:missing cipher
 PROV_R_MISSING_CONFIG_DATA:213:missing config data
 PROV_R_MISSING_CONSTANT:156:missing constant
+PROV_R_MISSING_EID:255:missing eid
 PROV_R_MISSING_KEY:128:missing key
 PROV_R_MISSING_MAC:150:missing mac
 PROV_R_MISSING_MESSAGE_DIGEST:129:missing message digest
diff --git a/doc/build.info b/doc/build.info
index ed6b7d1afe..d7ca9723b1 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -4643,6 +4643,10 @@ DEPEND[html/man7/EVP_KDF-SCRYPT.html]=man7/EVP_KDF-SCRYPT.pod
 GENERATE[html/man7/EVP_KDF-SCRYPT.html]=man7/EVP_KDF-SCRYPT.pod
 DEPEND[man/man7/EVP_KDF-SCRYPT.7]=man7/EVP_KDF-SCRYPT.pod
 GENERATE[man/man7/EVP_KDF-SCRYPT.7]=man7/EVP_KDF-SCRYPT.pod
+DEPEND[html/man7/EVP_KDF-SNMPKDF.html]=man7/EVP_KDF-SNMPKDF.pod
+GENERATE[html/man7/EVP_KDF-SNMPKDF.html]=man7/EVP_KDF-SNMPKDF.pod
+DEPEND[man/man7/EVP_KDF-SNMPKDF.7]=man7/EVP_KDF-SNMPKDF.pod
+GENERATE[man/man7/EVP_KDF-SNMPKDF.7]=man7/EVP_KDF-SNMPKDF.pod
 DEPEND[html/man7/EVP_KDF-SS.html]=man7/EVP_KDF-SS.pod
 GENERATE[html/man7/EVP_KDF-SS.html]=man7/EVP_KDF-SS.pod
 DEPEND[man/man7/EVP_KDF-SS.7]=man7/EVP_KDF-SS.pod
@@ -5213,6 +5217,7 @@ html/man7/EVP_KDF-PBKDF2.html \
 html/man7/EVP_KDF-PKCS12KDF.html \
 html/man7/EVP_KDF-PVKKDF.html \
 html/man7/EVP_KDF-SCRYPT.html \
+html/man7/EVP_KDF-SNMPKDF.html \
 html/man7/EVP_KDF-SS.html \
 html/man7/EVP_KDF-SSHKDF.html \
 html/man7/EVP_KDF-TLS13_KDF.html \
@@ -5373,6 +5378,7 @@ man/man7/EVP_KDF-PBKDF2.7 \
 man/man7/EVP_KDF-PKCS12KDF.7 \
 man/man7/EVP_KDF-PVKKDF.7 \
 man/man7/EVP_KDF-SCRYPT.7 \
+man/man7/EVP_KDF-SNMPKDF.7 \
 man/man7/EVP_KDF-SS.7 \
 man/man7/EVP_KDF-SSHKDF.7 \
 man/man7/EVP_KDF-TLS13_KDF.7 \
diff --git a/doc/man1/openssl-kdf.pod.in b/doc/man1/openssl-kdf.pod.in
index 6eed74d70d..ae153b4b81 100644
--- a/doc/man1/openssl-kdf.pod.in
+++ b/doc/man1/openssl-kdf.pod.in
@@ -141,7 +141,7 @@ This option is identical to the B<-mac> option.

 Specifies the name of a supported KDF algorithm which will be used.
 The supported algorithms names include TLS1-PRF, HKDF, SSKDF, PBKDF2,
-SSHKDF, X942KDF-ASN1, X942KDF-CONCAT, X963KDF and SCRYPT.
+SNMPKDF, SSHKDF, X942KDF-ASN1, X942KDF-CONCAT, X963KDF and SCRYPT.

 =back

@@ -175,6 +175,12 @@ Use SSKDF with Hash to create a hex-encoded derived key from a secret key, salt
                 -kdfopt hexkey:6dbdc23f045488 \
                 -kdfopt hexinfo:a1b2c3d4 SSKDF

+Use SNMPKDF to create a hex-encoded derived key from an engine ID, hash and password:
+
+    openssl kdf -keylen 20 -kdfopt digest:SHA1 \
+                -kdfopt pass:IFUcNbMl \
+                -kdfopt hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456 SNMPKDF
+
 Use SSHKDF to create a hex-encoded derived key from a secret key, hash and session_id:

     openssl kdf -keylen 16 -kdfopt digest:SHA2-256 \
@@ -208,6 +214,7 @@ L<EVP_KDF-SCRYPT(7)>,
 L<EVP_KDF-TLS1_PRF(7)>,
 L<EVP_KDF-PBKDF2(7)>,
 L<EVP_KDF-HKDF(7)>,
+L<EVP_KDF-SNMPKDF(7)>,
 L<EVP_KDF-SS(7)>,
 L<EVP_KDF-SSHKDF(7)>,
 L<EVP_KDF-X942-ASN1(7)>,
diff --git a/doc/man7/EVP_KDF-SNMPKDF.pod b/doc/man7/EVP_KDF-SNMPKDF.pod
new file mode 100644
index 0000000000..c0bcb70dee
--- /dev/null
+++ b/doc/man7/EVP_KDF-SNMPKDF.pod
@@ -0,0 +1,110 @@
+=pod
+
+=head1 NAME
+
+EVP_KDF-SNMPKDF - The SNMPKDF EVP_KDF implementation
+
+=head1 DESCRIPTION
+
+Support for computing the B<SNMPKDF> KDF through the B<EVP_KDF> API.
+
+The EVP_KDF-SNMPKDF algorithm implements the SNMPKDF key derivation function.
+It is defined in RFC 3414, appendix A.2.2 and is used by SNMP to derive
+encryption keys.
+Three inputs are required to perform key derivation: The hashing function
+(for example SHA1), the engine ID, and the password
+
+=head2 Identity
+
+"SNMPKDF" is the name for this implementation; it
+can be used with the EVP_KDF_fetch() function.
+
+=head2 Supported parameters
+
+The supported parameters are:
+
+=over 4
+
+=item "properties" (B<OSSL_KDF_PARAM_PROPERTIES>) <UTF8 string>
+
+=item "digest" (B<OSSL_KDF_PARAM_DIGEST>) <UTF8 string>
+
+=item "pass" (B<OSSL_KDF_PARAM_PASS>) <octet string>
+
+These parameters works as described in L<EVP_KDF(3)/PARAMETERS>.
+
+=item "eid" (B<OSSL_KDF_PARAM_SNMPKDF_EID>) <octet string>
+
+This parameter sets the snmpEngineID value for the KDF.
+If a value is already set, the contents are replaced.
+
+=back
+
+=head1 NOTES
+
+A context for SNMPKDF can be obtained by calling:
+
+ EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SNMPKDF", NULL);
+ EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf);
+
+The expected length of the SNMPKDF derivation output is the size of the I<digest>.
+Since the SNMPKDF output length can vary depending on the digest used, the caller should allocate a buffer of the
+digest size and pass that buffer in the L<EVP_KDF_derive(3)> function along with the expected length.
+
+=head1 EXAMPLES
+
+This example derives an 8 byte IV using SHA1 with a 1K "key" and appropriate
+"xcghash" and "session_id" values:
+
+ EVP_KDF *kdf;
+ EVP_KDF_CTX *kctx;
+ unsigned char eid[1024] = "01234...";
+ unsigned char pass[32] = "012345...";
+ unsigned char out[20];
+ size_t outlen = sizeof(out);
+ OSSL_PARAM params[6], *p = params;
+
+ kdf = EVP_KDF_fetch(NULL, "SNMPKDF", NULL);
+ kctx = EVP_KDF_CTX_new(kdf);
+ EVP_KDF_free(kdf);
+
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                         SN_sha1, strlen(SN_sha1));
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASS,
+                                          pass, sizeof(pass));
+ *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SNMPKDF_EID,
+                                          eid, sizeof(eid));
+ *p = OSSL_PARAM_construct_end();
+ if (EVP_KDF_derive(kctx, out, outlen, params) <= 0)
+     /* Error */
+
+
+=head1 CONFORMING TO
+
+RFC 3414, RFC 7860, NIST SP800-135
+
+=head1 SEE ALSO
+
+L<EVP_KDF(3)>,
+L<EVP_KDF_CTX_new(3)>,
+L<EVP_KDF_CTX_dup(3)>,
+L<EVP_KDF_CTX_free(3)>,
+L<EVP_KDF_CTX_set_params(3)>,
+L<EVP_KDF_CTX_get_kdf_size(3)>,
+L<EVP_KDF_derive(3)>,
+L<EVP_KDF(3)/PARAMETERS>
+
+=head1 HISTORY
+
+This functionality was added in OpenSSL 4.0.
+
+=head1 COPYRIGHT
+
+Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+
+Licensed under the Apache License 2.0 (the "License").  You may not use
+this file except in compliance with the License.  You can obtain a copy
+in the file LICENSE in the source distribution or at
+L<https://www.openssl.org/source/license.html>.
+
+=cut
diff --git a/doc/man7/OSSL_PROVIDER-FIPS.pod b/doc/man7/OSSL_PROVIDER-FIPS.pod
index 8ebf0c5cbd..b18140f554 100644
--- a/doc/man7/OSSL_PROVIDER-FIPS.pod
+++ b/doc/man7/OSSL_PROVIDER-FIPS.pod
@@ -117,6 +117,8 @@ KECCAK-KMAC is only used internally as a sub algorithm of KMAC.

 =item PBKDF2, see L<EVP_KDF-PBKDF2(7)>

+=item SNMPKDF, see L<EVP_KDF-SNMPKDF(7)>
+
 =item SSHKDF, see L<EVP_KDF-SSHKDF(7)>

 =item TLS1-PRF, see L<EVP_KDF-TLS1_PRF(7)>
@@ -483,6 +485,8 @@ Key agreement tests used with the "KAT_KA" type.

 =item "PBKDF2" (B<OSSL_SELF_TEST_DESC_KDF_PBKDF2>)

+=item "SNMPKDF" (B<OSSL_SELF_TEST_DESC_KDF_SNMPKDF>)
+
 =item "SSHKDF" (B<OSSL_SELF_TEST_DESC_KDF_SSHKDF>)

 =item "TLS12_PRF" (B<OSSL_SELF_TEST_DESC_KDF_TLS12_PRF>)
diff --git a/doc/man7/OSSL_PROVIDER-default.pod b/doc/man7/OSSL_PROVIDER-default.pod
index f54815bd1c..f0743d99d7 100644
--- a/doc/man7/OSSL_PROVIDER-default.pod
+++ b/doc/man7/OSSL_PROVIDER-default.pod
@@ -139,6 +139,8 @@ The OpenSSL default provider supports these operations and algorithms:

 =item PKCS12KDF, see L<EVP_KDF-PKCS12KDF(7)>

+=item SNMPKDF, see L<EVP_KDF-SNMPKDF(7)>
+
 =item SSHKDF, see L<EVP_KDF-SSHKDF(7)>

 =item TLS1-PRF, see L<EVP_KDF-TLS1_PRF(7)>
diff --git a/include/openssl/core_names.h.in b/include/openssl/core_names.h.in
index 779acf02c0..e0a4c5e632 100644
--- a/include/openssl/core_names.h.in
+++ b/include/openssl/core_names.h.in
@@ -75,6 +75,7 @@ extern "C" {
 #define OSSL_KDF_NAME_PBKDF1 "PBKDF1"
 #define OSSL_KDF_NAME_PBKDF2 "PBKDF2"
 #define OSSL_KDF_NAME_SCRYPT "SCRYPT"
+#define OSSL_KDF_NAME_SNMPKDF "SNMPKDF"
 #define OSSL_KDF_NAME_SSHKDF "SSHKDF"
 #define OSSL_KDF_NAME_SSKDF "SSKDF"
 #define OSSL_KDF_NAME_TLS1_PRF "TLS1-PRF"
diff --git a/include/openssl/proverr.h b/include/openssl/proverr.h
index a8799f6757..fdfa8916a2 100644
--- a/include/openssl/proverr.h
+++ b/include/openssl/proverr.h
@@ -97,6 +97,7 @@
 #define PROV_R_MISSING_CIPHER 155
 #define PROV_R_MISSING_CONFIG_DATA 213
 #define PROV_R_MISSING_CONSTANT 156
+#define PROV_R_MISSING_EID 255
 #define PROV_R_MISSING_KEY 128
 #define PROV_R_MISSING_MAC 150
 #define PROV_R_MISSING_MESSAGE_DIGEST 129
diff --git a/include/openssl/self_test.h b/include/openssl/self_test.h
index 6e671c1a98..8dab9b6f99 100644
--- a/include/openssl/self_test.h
+++ b/include/openssl/self_test.h
@@ -83,6 +83,7 @@ extern "C" {
 #define OSSL_SELF_TEST_DESC_KDF_X963KDF "X963KDF"
 #define OSSL_SELF_TEST_DESC_KDF_X942KDF "X942KDF"
 #define OSSL_SELF_TEST_DESC_KDF_PBKDF2 "PBKDF2"
+#define OSSL_SELF_TEST_DESC_KDF_SNMPKDF "SNMPKDF"
 #define OSSL_SELF_TEST_DESC_KDF_SSHKDF "SSHKDF"
 #define OSSL_SELF_TEST_DESC_KDF_TLS12_PRF "TLS12_PRF"
 #define OSSL_SELF_TEST_DESC_KDF_KBKDF "KBKDF"
diff --git a/providers/common/provider_err.c b/providers/common/provider_err.c
index cf956168b3..6cdb728333 100644
--- a/providers/common/provider_err.c
+++ b/providers/common/provider_err.c
@@ -142,6 +142,7 @@ static const ERR_STRING_DATA PROV_str_reasons[] = {
     { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_CONFIG_DATA),
         "missing config data" },
     { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_CONSTANT), "missing constant" },
+    { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_EID), "missing eid" },
     { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_KEY), "missing key" },
     { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_MAC), "missing mac" },
     { ERR_PACK(ERR_LIB_PROV, 0, PROV_R_MISSING_MESSAGE_DIGEST),
diff --git a/providers/defltprov.c b/providers/defltprov.c
index 9cc14ff7ec..50397f2189 100644
--- a/providers/defltprov.c
+++ b/providers/defltprov.c
@@ -366,6 +366,7 @@ static const OSSL_ALGORITHM deflt_kdfs[] = {
     { PROV_NAMES_SSKDF, "provider=default", ossl_kdf_sskdf_functions },
     { PROV_NAMES_PBKDF2, "provider=default", ossl_kdf_pbkdf2_functions },
     { PROV_NAMES_PKCS12KDF, "provider=default", ossl_kdf_pkcs12_functions },
+    { PROV_NAMES_SNMPKDF, "provider=default", ossl_kdf_snmpkdf_functions },
     { PROV_NAMES_SSHKDF, "provider=default", ossl_kdf_sshkdf_functions },
     { PROV_NAMES_X963KDF, "provider=default", ossl_kdf_x963_kdf_functions },
     { PROV_NAMES_TLS1_PRF, "provider=default", ossl_kdf_tls1_prf_functions },
diff --git a/providers/fips/fipsprov.c b/providers/fips/fipsprov.c
index d613740194..8967a21e9a 100644
--- a/providers/fips/fipsprov.c
+++ b/providers/fips/fipsprov.c
@@ -428,6 +428,7 @@ static const OSSL_ALGORITHM fips_macs_internal[] = {
             ossl_kdf_tls1_3_kdf_functions },                                                 \
         { PROV_NAMES_SSKDF, FIPS_DEFAULT_PROPERTIES, ossl_kdf_sskdf_functions },             \
         { PROV_NAMES_PBKDF2, FIPS_DEFAULT_PROPERTIES, ossl_kdf_pbkdf2_functions },           \
+        { PROV_NAMES_SNMPKDF, FIPS_DEFAULT_PROPERTIES, ossl_kdf_snmpkdf_functions },         \
         { PROV_NAMES_SSHKDF, FIPS_DEFAULT_PROPERTIES, ossl_kdf_sshkdf_functions },           \
         { PROV_NAMES_X963KDF, FIPS_DEFAULT_PROPERTIES,                                       \
             ossl_kdf_x963_kdf_functions },                                                   \
diff --git a/providers/fips/self_test_data.inc b/providers/fips/self_test_data.inc
index b407324314..2442038eb1 100644
--- a/providers/fips/self_test_data.inc
+++ b/providers/fips/self_test_data.inc
@@ -512,6 +512,25 @@ static const ST_KAT_PARAM hkdf_params[] = {
     ST_KAT_PARAM_END()
 };

+static const char snmpkdf_digest[] = "SHA1";
+static const unsigned char snmpkdf_eid[] = {
+    0x80, 0x00, 0x02, 0xb8, 0x05, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde,
+    0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56,
+    0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56
+};
+static const unsigned char snmpkdf_password[] = { 0x74, 0x63, 0x6f, 0x54, 0x49, 0x48, 0x6d, 0x77,
+                                                  0x63, 0x46, 0x6c, 0x50, 0x52, 0x65, 0x52, 0x4a };
+static const unsigned char snmpkdf_expected[] = {
+    0x3c, 0xd1, 0x21, 0xcb, 0xf3, 0xe8, 0xbf, 0x9e, 0x6d, 0x80, 0xf5, 0xf0,
+    0x53, 0x83, 0x5b, 0x3c, 0x24, 0x1c, 0xd2, 0x1e
+};
+static const ST_KAT_PARAM snmpkdf_params[] = {
+    ST_KAT_PARAM_UTF8STRING(OSSL_KDF_PARAM_DIGEST, snmpkdf_digest),
+    ST_KAT_PARAM_OCTET(OSSL_KDF_PARAM_SNMPKDF_EID, snmpkdf_eid),
+    ST_KAT_PARAM_OCTET(OSSL_KDF_PARAM_PASSWORD, snmpkdf_password),
+    ST_KAT_PARAM_END()
+};
+
 static const char sskdf_digest[] = "SHA256";
 static const unsigned char sskdf_secret[] = {
     0x6d, 0xbd, 0xc2, 0x3f, 0x04, 0x54, 0x88, 0xe4,
@@ -824,6 +843,13 @@ static const ST_KAT_KDF st_kat_kdf_tests[] =
         hkdf_params,
         ITM(hkdf_expected)
     },
+    {
+        OSSL_SELF_TEST_DESC_KDF_SNMPKDF,
+        OSSL_KDF_NAME_SNMPKDF,
+        0,
+        snmpkdf_params,
+        ITM(snmpkdf_expected)
+    },
     {
         OSSL_SELF_TEST_DESC_KDF_SSKDF,
         OSSL_KDF_NAME_SSKDF,
diff --git a/providers/implementations/include/prov/implementations.h b/providers/implementations/include/prov/implementations.h
index 3ecf458182..70fff1b797 100644
--- a/providers/implementations/include/prov/implementations.h
+++ b/providers/implementations/include/prov/implementations.h
@@ -290,6 +290,7 @@ extern const OSSL_DISPATCH ossl_kdf_hkdf_sha256_functions[];
 extern const OSSL_DISPATCH ossl_kdf_hkdf_sha384_functions[];
 extern const OSSL_DISPATCH ossl_kdf_hkdf_sha512_functions[];
 extern const OSSL_DISPATCH ossl_kdf_tls1_3_kdf_functions[];
+extern const OSSL_DISPATCH ossl_kdf_snmpkdf_functions[];
 extern const OSSL_DISPATCH ossl_kdf_sshkdf_functions[];
 extern const OSSL_DISPATCH ossl_kdf_sskdf_functions[];
 extern const OSSL_DISPATCH ossl_kdf_x963_kdf_functions[];
diff --git a/providers/implementations/include/prov/names.h b/providers/implementations/include/prov/names.h
index e5cab7f1bb..bc98202879 100644
--- a/providers/implementations/include/prov/names.h
+++ b/providers/implementations/include/prov/names.h
@@ -289,6 +289,7 @@
 #define PROV_NAMES_PBKDF1 "PBKDF1"
 #define PROV_NAMES_PBKDF2 "PBKDF2:1.2.840.113549.1.5.12"
 #define PROV_NAMES_PVKKDF "PVKKDF"
+#define PROV_NAMES_SNMPKDF "SNMPKDF"
 #define PROV_NAMES_SSHKDF "SSHKDF"
 #define PROV_NAMES_X963KDF "X963KDF:X942KDF-CONCAT"
 #define PROV_NAMES_X942KDF_ASN1 "X942KDF-ASN1:X942KDF"
diff --git a/providers/implementations/kdfs/build.info b/providers/implementations/kdfs/build.info
index 17f9e6542d..b41a730e57 100644
--- a/providers/implementations/kdfs/build.info
+++ b/providers/implementations/kdfs/build.info
@@ -11,6 +11,7 @@ $PVKKDF_GOAL=../../liblegacy.a
 $PKCS12KDF_GOAL=../../libdefault.a
 $SSKDF_GOAL=../../libdefault.a ../../libfips.a
 $SCRYPT_GOAL=../../libdefault.a
+$SNMPKDF_GOAL=../../libdefault.a ../../libfips.a
 $SSHKDF_GOAL=../../libdefault.a ../../libfips.a
 $X942KDF_GOAL=../../libdefault.a ../../libfips.a
 $HMAC_DRBG_KDF_GOAL=../../libdefault.a ../../libfips.a
@@ -35,6 +36,7 @@ SOURCE[$PKCS12KDF_GOAL]=pkcs12kdf.c
 SOURCE[$SSKDF_GOAL]=sskdf.c

 SOURCE[$SCRYPT_GOAL]=scrypt.c
+SOURCE[$SNMPKDF_GOAL]=snmpkdf.c
 SOURCE[$SSHKDF_GOAL]=sshkdf.c
 SOURCE[$X942KDF_GOAL]=x942kdf.c
 DEPEND[x942kdf.o]=../../common/include/prov/der_wrap.h
diff --git a/providers/implementations/kdfs/snmpkdf.c b/providers/implementations/kdfs/snmpkdf.c
new file mode 100644
index 0000000000..9a55b90b33
--- /dev/null
+++ b/providers/implementations/kdfs/snmpkdf.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <openssl/evp.h>
+#include <openssl/kdf.h>
+#include <openssl/sha.h>
+#include <openssl/core_names.h>
+#include <openssl/proverr.h>
+#include "internal/cryptlib.h"
+#include "internal/numbers.h"
+#include "crypto/evp.h"
+#include "prov/provider_ctx.h"
+#include "prov/providercommon.h"
+#include "prov/implementations.h"
+#include "prov/provider_util.h"
+#include "providers/implementations/kdfs/snmpkdf.inc"
+
+#define KDF_SNMP_PASSWORD_HASH_AMOUNT (1024 * 1024)
+#define KDF_SNMP_MIN_PASSWORD_LEN     8
+
+/* See RFC 3414, Appendix A.2.2 */
+/* See NIST SP800-135 Section 6.8 */
+static OSSL_FUNC_kdf_newctx_fn kdf_snmpkdf_new;
+static OSSL_FUNC_kdf_dupctx_fn kdf_snmpkdf_dup;
+static OSSL_FUNC_kdf_freectx_fn kdf_snmpkdf_free;
+static OSSL_FUNC_kdf_reset_fn kdf_snmpkdf_reset;
+static OSSL_FUNC_kdf_derive_fn kdf_snmpkdf_derive;
+static OSSL_FUNC_kdf_settable_ctx_params_fn kdf_snmpkdf_settable_ctx_params;
+static OSSL_FUNC_kdf_set_ctx_params_fn kdf_snmpkdf_set_ctx_params;
+static OSSL_FUNC_kdf_gettable_ctx_params_fn kdf_snmpkdf_gettable_ctx_params;
+static OSSL_FUNC_kdf_get_ctx_params_fn kdf_snmpkdf_get_ctx_params;
+
+static int SNMPKDF(const EVP_MD *evp_md,
+                   const unsigned char *eid, size_t eid_len,
+                   unsigned char *password, size_t password_len,
+                   unsigned char *key, size_t keylen);
+
+typedef struct {
+    /* Warning: Any changes to this structure may require you to update kdf_snmpkdf_dup */
+    void *provctx;
+    PROV_DIGEST digest;
+    unsigned char *eid;
+    size_t eid_len;
+    unsigned char *password;
+    size_t password_len;
+} KDF_SNMPKDF;
+
+static void *kdf_snmpkdf_new(void *provctx)
+{
+    KDF_SNMPKDF *ctx;
+
+    if (!ossl_prov_is_running())
+        return NULL;
+
+    if ((ctx = OPENSSL_zalloc(sizeof(*ctx))) != NULL)
+        ctx->provctx = provctx;
+    return ctx;
+}
+
+static void *kdf_snmpkdf_dup(void *vctx)
+{
+    const KDF_SNMPKDF *src = (const KDF_SNMPKDF *)vctx;
+    KDF_SNMPKDF *dest;
+
+    dest = kdf_snmpkdf_new(src->provctx);
+    if (dest != NULL) {
+        if (!ossl_prov_memdup(src->eid, src->eid_len,
+                              &dest->eid, &dest->eid_len)
+                || !ossl_prov_memdup(src->password, src->password_len,
+                                     &dest->password, &dest->password_len)
+                || !ossl_prov_digest_copy(&dest->digest, &src->digest))
+            goto err;
+    }
+    return dest;
+
+ err:
+    kdf_snmpkdf_free(dest);
+    return NULL;
+}
+
+static void kdf_snmpkdf_free(void *vctx)
+{
+    KDF_SNMPKDF *ctx = (KDF_SNMPKDF *)vctx;
+
+    if (ctx != NULL) {
+        kdf_snmpkdf_reset(ctx);
+        OPENSSL_free(ctx);
+    }
+}
+
+static void kdf_snmpkdf_reset(void *vctx)
+{
+    KDF_SNMPKDF *ctx = (KDF_SNMPKDF *)vctx;
+    void *provctx = ctx->provctx;
+
+    ossl_prov_digest_reset(&ctx->digest);
+    OPENSSL_clear_free(ctx->eid, ctx->eid_len);
+    OPENSSL_clear_free(ctx->password, ctx->password_len);
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->provctx = provctx;
+}
+
+static int snmpkdf_set_membuf(unsigned char **dst, size_t *dst_len,
+                              const OSSL_PARAM *p)
+{
+    OPENSSL_clear_free(*dst, *dst_len);
+    *dst = NULL;
+    *dst_len = 0;
+    return OSSL_PARAM_get_octet_string(p, (void **)dst, 0, dst_len);
+}
+
+static int kdf_snmpkdf_derive(void *vctx, unsigned char *key, size_t keylen,
+                              const OSSL_PARAM params[])
+{
+    KDF_SNMPKDF *ctx = (KDF_SNMPKDF *)vctx;
+    const EVP_MD *md;
+
+    if (!ossl_prov_is_running() || !kdf_snmpkdf_set_ctx_params(ctx, params))
+        return 0;
+
+    md = ossl_prov_digest_md(&ctx->digest);
+    if (md == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_MESSAGE_DIGEST);
+        return 0;
+    }
+    if (ctx->eid == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_EID);
+        return 0;
+    }
+    if (ctx->password == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_PASS);
+        return 0;
+    }
+
+    return SNMPKDF(md, ctx->eid, ctx->eid_len,
+                   ctx->password, ctx->password_len,
+                   key, keylen);
+}
+
+static int kdf_snmpkdf_set_ctx_params(void *vctx, const OSSL_PARAM params[])
+{
+    struct snmp_set_ctx_params_st p;
+    KDF_SNMPKDF *ctx = vctx;
+    OSSL_LIB_CTX *libctx = PROV_LIBCTX_OF(ctx->provctx);
+#ifdef FIPS_MODULE
+    const EVP_MD *md = NULL;
+#endif
+
+    if (params == NULL)
+        return 1;
+
+    if (ctx == NULL || !snmp_set_ctx_params_decoder(params, &p))
+        return 0;
+
+    if (p.digest != NULL) {
+        if (!ossl_prov_digest_load(&ctx->digest, p.digest, p.propq, libctx))
+            return 0;
+#ifdef FIPS_MODULE
+        md = ossl_prov_digest_md(&ctx->digest);
+        if (!EVP_MD_is_a(md, SN_sha1))
+            return 0;
+#endif
+    }
+
+    if (p.pw != NULL) {
+        if (!snmpkdf_set_membuf(&ctx->password, &ctx->password_len, p.pw))
+            return 0;
+        if ((ctx->password_len > KDF_SNMP_PASSWORD_HASH_AMOUNT) ||
+            (ctx->password_len < KDF_SNMP_MIN_PASSWORD_LEN))
+            return 0;
+    }
+
+    if (p.eid != NULL && !snmpkdf_set_membuf(&ctx->eid, &ctx->eid_len, p.eid))
+        return 0;
+
+    return 1;
+}
+
+static const OSSL_PARAM *kdf_snmpkdf_settable_ctx_params(ossl_unused void *ctx,
+                                                         ossl_unused void *p_ctx)
+{
+    return snmp_set_ctx_params_list;
+}
+
+static size_t kdf_snmpkdf_size(KDF_SNMPKDF *ctx)
+{
+    int len;
+    const EVP_MD *md = NULL;
+
+    md = ossl_prov_digest_md(&ctx->digest);
+    if (md == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_MESSAGE_DIGEST);
+        return 0;
+    }
+    len = EVP_MD_get_size(md);
+    return (len <= 0) ? 0 : (size_t)len;
+}
+
+static int kdf_snmpkdf_get_ctx_params(void *vctx, OSSL_PARAM params[])
+{
+    struct snmp_get_ctx_params_st p;
+    KDF_SNMPKDF *ctx = vctx;
+
+    if (ctx == NULL || !snmp_get_ctx_params_decoder(params, &p))
+        return 0;
+
+    if (p.size != NULL) {
+        size_t sz = kdf_snmpkdf_size(ctx);
+
+        if (sz == 0)
+            return 0;
+        if (!OSSL_PARAM_set_size_t(p.size, sz))
+            return 0;
+    }
+    return 1;
+}
+
+static const OSSL_PARAM *kdf_snmpkdf_gettable_ctx_params(ossl_unused void *ctx,
+                                                         ossl_unused void *p_ctx)
+{
+    return snmp_get_ctx_params_list;
+}
+
+const OSSL_DISPATCH ossl_kdf_snmpkdf_functions[] = {
+    { OSSL_FUNC_KDF_NEWCTX, (void(*)(void))kdf_snmpkdf_new },
+    { OSSL_FUNC_KDF_DUPCTX, (void(*)(void))kdf_snmpkdf_dup },
+    { OSSL_FUNC_KDF_FREECTX, (void(*)(void))kdf_snmpkdf_free },
+    { OSSL_FUNC_KDF_RESET, (void(*)(void))kdf_snmpkdf_reset },
+    { OSSL_FUNC_KDF_DERIVE, (void(*)(void))kdf_snmpkdf_derive },
+    { OSSL_FUNC_KDF_SETTABLE_CTX_PARAMS,
+      (void(*)(void))kdf_snmpkdf_settable_ctx_params },
+    { OSSL_FUNC_KDF_SET_CTX_PARAMS, (void(*)(void))kdf_snmpkdf_set_ctx_params },
+    { OSSL_FUNC_KDF_GETTABLE_CTX_PARAMS,
+      (void(*)(void))kdf_snmpkdf_gettable_ctx_params },
+    { OSSL_FUNC_KDF_GET_CTX_PARAMS, (void(*)(void))kdf_snmpkdf_get_ctx_params },
+    { 0, NULL }
+};
+
+/*
+ * SNMPKDF - In compliance with SP800-135 and RFC7860, calculate
+ *           a master key using the engine ID and password.
+ *
+ * Denote engineLength and passwordlen to be the lengths (in bytes) of an
+ * snmpEngineID and a password, respectively.
+ *
+ * Let N = (1024*1024)/passwordlen
+ *
+ * Expanded_password = the leftmost 1048576 bytes of the string of N
+ * repetitions of the password.
+ *
+ * Derived_password = SHA-1 (Expanded_password). The Derived_password
+ * is the output of hashing the Expanded_password by SHA-1.
+ *
+ * Let Shared_key to be the key that the user shares with the authoritative
+ * SNMP engine with ID snmpEngineID. The Shared_key is generated as follows:
+ *
+ * Shared_key = SHA-1(Derived_password || snmpEngineID || Derived_password).
+ *
+ *     e_id -         engine ID(eid)
+ *     e_len -        engineID length
+ *     password -     password
+ *     password_len - password length
+ *     okey -         pointer to key output, FIPS testing limited to SHA-1.
+ *     okeylen -      key output length
+ *     return -       1 pass 0 for error
+ */
+static int SNMPKDF(const EVP_MD *evp_md,
+                   const unsigned char *e_id, size_t e_len,
+                   unsigned char *password, size_t password_len,
+                   unsigned char *okey, size_t okeylen)
+{
+    EVP_MD_CTX *md = NULL;
+    unsigned char digest[EVP_MAX_MD_SIZE];
+    size_t mdsize = 0, len = 0;
+    unsigned int md_len = 0;
+    int ret = 0;
+
+    /* Limited to SHA-1 and SHA-2 hashes presently */
+    if (okey == NULL || okeylen == 0)
+        return 0;
+
+    md = EVP_MD_CTX_new();
+    if (md == NULL) {
+        ERR_raise(ERR_LIB_PROV, PROV_R_MISSING_MESSAGE_DIGEST);
+        goto err;
+    }
+
+    mdsize = EVP_MD_get_size(evp_md);
+    if (mdsize <= 0 || mdsize < okeylen)
+        goto err;
+
+    if (!EVP_DigestInit_ex(md, evp_md, NULL))
+        goto err;
+
+    for (len = 0; len < KDF_SNMP_PASSWORD_HASH_AMOUNT - password_len; len += password_len) {
+        if (!EVP_DigestUpdate(md, password, password_len))
+            goto err;
+    }
+
+    if (!EVP_DigestUpdate(md, password, KDF_SNMP_PASSWORD_HASH_AMOUNT - len)
+        || !EVP_DigestFinal_ex(md, digest, &md_len)
+        || !EVP_DigestInit_ex(md, evp_md, NULL)
+        || !EVP_DigestUpdate(md, digest, mdsize)
+        || !EVP_DigestUpdate(md, e_id, e_len)
+        || !EVP_DigestUpdate(md, digest, mdsize)
+        || !EVP_DigestFinal_ex(md, digest, &md_len))
+        goto err;
+
+    memcpy(okey, digest, okeylen);
+
+    ret = 1;
+
+err:
+    EVP_MD_CTX_free(md);
+    OPENSSL_cleanse(digest, EVP_MAX_MD_SIZE);
+    return ret;
+}
diff --git a/providers/implementations/kdfs/snmpkdf.inc.in b/providers/implementations/kdfs/snmpkdf.inc.in
new file mode 100644
index 0000000000..c14cd209fb
--- /dev/null
+++ b/providers/implementations/kdfs/snmpkdf.inc.in
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the \"License\").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+{-
+use OpenSSL::paramnames qw(produce_param_decoder);
+-}
+
+{- produce_param_decoder('snmp_set_ctx_params',
+                         (['OSSL_KDF_PARAM_PROPERTIES',    'propq',  'utf8_string'],
+                          ['OSSL_KDF_PARAM_DIGEST',        'digest', 'utf8_string'],
+                          ['OSSL_KDF_PARAM_SNMPKDF_EID',   'eid',    'octet_string'],
+                          ['OSSL_KDF_PARAM_PASSWORD',      'pw',     'octet_string'],
+                         )); -}
+
+{- produce_param_decoder('snmp_get_ctx_params',
+                         (['OSSL_KDF_PARAM_SIZE',          'size', 'size_t'],
+                         )); -}
diff --git a/test/recipes/30-test_evp.t b/test/recipes/30-test_evp.t
index 2ac6ab70d9..98af32086d 100644
--- a/test/recipes/30-test_evp.t
+++ b/test/recipes/30-test_evp.t
@@ -56,6 +56,7 @@ my @files = qw(
                 evpkdf_kbkdf_kmac.txt
                 evpkdf_pbkdf1.txt
                 evpkdf_pbkdf2.txt
+                evpkdf_snmp.txt
                 evpkdf_ss.txt
                 evpkdf_ssh.txt
                 evpkdf_tls12_prf.txt
diff --git a/test/recipes/30-test_evp_data/evpkdf_snmp.txt b/test/recipes/30-test_evp_data/evpkdf_snmp.txt
new file mode 100644
index 0000000000..1c062eb55e
--- /dev/null
+++ b/test/recipes/30-test_evp_data/evpkdf_snmp.txt
@@ -0,0 +1,107 @@
+#
+# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+# Tests start with one of these keywords
+#       Cipher Decrypt Derive Digest Encoding KDF MAC PBE
+#       PrivPubKeyPair Sign Verify VerifyRecover
+# and continue until a blank line. Lines starting with a pound sign are ignored.
+
+Title = SNMPKDF tests (from NIST ACVP 1.0 test vectors)
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456
+Ctrl.pass = pass:IFUcNbMl
+Output = 6ab11fc4cada4ce96f4cc5cf8f37acfb18c1d992
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456
+Ctrl.pass = pass:bAuDCHTs
+Output = 907a086a6c038749fef2cbdadc1108bba649c02f
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456
+Ctrl.pass = pass:jXCaBOBTSnQkYseOecvUCyAiMOMjNuZx
+Output = 8db38c0266e1cf1edd724e1c7fe9de73dca8acb6
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456
+Ctrl.pass = pass:WgXMvXGNpSoaeEmIqBCbSzsfygkxzdMK
+Output = cf50fac5db4f84281ce4a0569ded11beeefc2596
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:000002b87766554433221100
+Ctrl.pass = pass:nozniMRj
+Output = 90e6cd5df0618e117df1fde6f01e22e010489311
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:000002b87766554433221100
+Ctrl.pass = pass:WmSlorrf
+Output = a13096b909ae1c6034337d203eca7ab53d3da47e
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:000002B87766554433221100
+Ctrl.pass = pass:DnlIshNwRflhNzGYiNUOHSktxjntPiOB
+Output = e1c4636f8fe00a90012489312a0f778ab001f3ff
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:000002B87766554433221100
+Ctrl.pass = pass:gdRkZksOndMLQCLmDdrWCMFBZQrerfTX
+Output = BB1640B442255E9C1C6357F64230506B011DCD0D
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.eid = hexeid:000002B87766554433221100
+Ctrl.pass = pass:gdRkZksOndMLQCLmDdrWCMFBZQrerfTX
+Output = BB1640B442255E9C1C6357F64230506B011DCD0D
+Result = KDF_DERIVE_ERROR
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.pass = pass:gdRkZksOndMLQCLmDdrWCMFBZQrerfTX
+Output = BB1640B442255E9C1C6357F64230506B011DCD0D
+Result = KDF_DERIVE_ERROR
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:000002B87766554433221100
+Output = BB1640B442255E9C1C6357F64230506B011DCD0D
+Result = KDF_DERIVE_ERROR
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:FAIL
+Ctrl.eid = hexeid:000002B87766554433221100
+Ctrl.pass = pass:gdRkZksOndMLQCLmDdrWCMFBZQrerfTX
+Output = BB1640B442255E9C1C6357F64230506B011DCD0D
+Result = KDF_CTRL_ERROR
+
+FIPSversion = >=4.0.0
+KDF = SNMPKDF
+Ctrl.digest = digest:SHA1
+Ctrl.eid = hexeid:800002b805123456789abcdef0123456789abcdef0123456789abcdef0123456
+Ctrl.pass = pass:IFUcNbM
+Output = 6ab11fc4cada4ce96f4cc5cf8f37acfb18c1d992
+Result = KDF_CTRL_ERROR
diff --git a/util/perl/OpenSSL/paramnames.pm b/util/perl/OpenSSL/paramnames.pm
index 744edb10e3..24c339c944 100644
--- a/util/perl/OpenSSL/paramnames.pm
+++ b/util/perl/OpenSSL/paramnames.pm
@@ -222,6 +222,7 @@ my %params = (
     'OSSL_KDF_PARAM_SCRYPT_MAXMEM' => "maxmem_bytes",            # uint64_t
     'OSSL_KDF_PARAM_INFO' =>         "info",                     # octet string
     'OSSL_KDF_PARAM_SEED' =>         "seed",                     # octet string
+    'OSSL_KDF_PARAM_SNMPKDF_EID' =>  "eid",                      # octet string
     'OSSL_KDF_PARAM_SSHKDF_XCGHASH' => "xcghash",                # octet string
     'OSSL_KDF_PARAM_SSHKDF_SESSION_ID' => "session_id",          # octet string
     'OSSL_KDF_PARAM_SSHKDF_TYPE' =>  "type",                     # int