Commit 89c6d4d405 for openssl.org

commit 89c6d4d40562f5a293a6b50113dfafed32b0a816
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date:   Tue Apr 21 19:10:34 2026 +0200

    Add support for Java keytool PKCS#12 files with symmetric secret keys

    Introduce PKCS12_PARSE_CTX opaque context for PKCS12_parse_ex(), replacing
    the growing parameter list pattern. Add PKCS12_decrypt_secretbag() for
    decrypting secret bags and PKCS12_SAFEBAG_get1_skey() for converting
    decrypted PKCS8 data to EVP_SKEY, following the existing private key
    pattern (PKCS12_decrypt_skey_ex + EVP_PKCS82PKEY_ex).

    Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>

    Reviewed-by: Mounir Idrassi <mounir.idrassi@idrix.fr>
    Reviewed-by: Simo Sorce <simo@redhat.com>
    MergeDate: Thu Sep 24 07:34:22 2026
    (Merged from https://github.com/openssl/openssl/pull/30937)

diff --git a/.gitignore b/.gitignore
index 82abffd4ba..c3d9526f2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,6 +51,7 @@
 /include/openssl/ct.h
 /include/openssl/err.h
 /include/openssl/ess.h
+/include/openssl/evp.h
 /include/openssl/fipskey.h
 /include/openssl/lhash.h
 /include/openssl/ocsp.h
diff --git a/build.info b/build.info
index 80aeff4f94..7d129700e5 100644
--- a/build.info
+++ b/build.info
@@ -60,6 +60,7 @@ DEPEND[]=include/openssl/asn1.h \
          include/openssl/ct.h \
          include/openssl/err.h \
          include/openssl/ess.h \
+         include/openssl/evp.h \
          include/openssl/fipskey.h \
          include/openssl/lhash.h \
          {- $err_headers -} \
@@ -200,6 +201,7 @@ GENERATE[include/openssl/crypto.h]=include/openssl/crypto.h.in
 GENERATE[include/openssl/ct.h]=include/openssl/ct.h.in
 GENERATE[include/openssl/err.h]=include/openssl/err.h.in
 GENERATE[include/openssl/ess.h]=include/openssl/ess.h.in
+GENERATE[include/openssl/evp.h]=include/openssl/evp.h.in
 GENERATE[include/openssl/fipskey.h]=include/openssl/fipskey.h.in
 GENERATE[include/openssl/lhash.h]=include/openssl/lhash.h.in
 GENERATE[include/openssl/ocsp.h]=include/openssl/ocsp.h.in
diff --git a/crypto/pkcs12/p12_add.c b/crypto/pkcs12/p12_add.c
index 1217317783..1d330e4701 100644
--- a/crypto/pkcs12/p12_add.c
+++ b/crypto/pkcs12/p12_add.c
@@ -149,8 +149,9 @@ PKCS7 *PKCS12_pack_p7encdata(int pbe_nid, const char *pass, int passlen,
         iter, bags, NULL, NULL);
 }

-STACK_OF(PKCS12_SAFEBAG) *PKCS12_unpack_p7encdata(PKCS7 *p7, const char *pass,
-    int passlen)
+STACK_OF(PKCS12_SAFEBAG) *ossl_pkcs12_unpack_p7encdata_ex(PKCS7 *p7,
+    const char *pass, int passlen,
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
     if (!PKCS7_type_is_encrypted(p7))
         return NULL;
@@ -164,6 +165,13 @@ STACK_OF(PKCS12_SAFEBAG) *PKCS12_unpack_p7encdata(PKCS7 *p7, const char *pass,
         ASN1_ITEM_rptr(PKCS12_SAFEBAGS),
         pass, passlen,
         p7->d.encrypted->enc_data->enc_data, 1,
+        libctx, propq);
+}
+
+STACK_OF(PKCS12_SAFEBAG) *PKCS12_unpack_p7encdata(PKCS7 *p7, const char *pass,
+    int passlen)
+{
+    return ossl_pkcs12_unpack_p7encdata_ex(p7, pass, passlen,
         p7->ctx.libctx, p7->ctx.propq);
 }

@@ -180,6 +188,35 @@ PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_skey(const PKCS12_SAFEBAG *bag,
     return PKCS12_decrypt_skey_ex(bag, pass, passlen, NULL, NULL);
 }

+PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_secretbag(const PKCS12_SAFEBAG *bag,
+    const char *pass, int passlen,
+    OSSL_LIB_CTX *ctx, const char *propq)
+{
+    const ASN1_TYPE *bag_obj;
+    const unsigned char *p;
+    X509_SIG *p8enc = NULL;
+    PKCS8_PRIV_KEY_INFO *p8inf = NULL;
+
+    if (PKCS12_SAFEBAG_get_nid(bag) != NID_secretBag)
+        return NULL;
+    if (PKCS12_SAFEBAG_get_bag_nid(bag) != NID_pkcs8ShroudedKeyBag)
+        return NULL;
+
+    bag_obj = PKCS12_SAFEBAG_get0_bag_obj(bag);
+    if (bag_obj == NULL || bag_obj->type != V_ASN1_OCTET_STRING)
+        return NULL;
+
+    p = ASN1_STRING_get0_data(bag_obj->value.octet_string);
+    p8enc = d2i_X509_SIG(NULL, &p,
+        (unsigned int)ASN1_STRING_get_length(bag_obj->value.octet_string));
+    if (p8enc == NULL)
+        return NULL;
+
+    p8inf = PKCS8_decrypt_ex(p8enc, pass, passlen, ctx, propq);
+    X509_SIG_free(p8enc);
+    return p8inf;
+}
+
 int PKCS12_pack_authsafes(PKCS12 *p12, STACK_OF(PKCS7) *safes)
 {
     if (ASN1_item_pack(safes, ASN1_ITEM_rptr(PKCS12_AUTHSAFES),
@@ -188,10 +225,10 @@ int PKCS12_pack_authsafes(PKCS12 *p12, STACK_OF(PKCS7) *safes)
     return 0;
 }

-STACK_OF(PKCS7) *PKCS12_unpack_authsafes(const PKCS12 *p12)
+STACK_OF(PKCS7) *ossl_pkcs12_unpack_authsafes_ex(const PKCS12 *p12,
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
     STACK_OF(PKCS7) *p7s;
-    PKCS7_CTX *p7ctx;
     PKCS7 *p7;
     int i;

@@ -205,16 +242,15 @@ STACK_OF(PKCS7) *PKCS12_unpack_authsafes(const PKCS12 *p12)
         return NULL;
     }

-    p7ctx = &p12->authsafes->ctx;
     p7s = ASN1_item_unpack_ex(p12->authsafes->d.data,
-        ASN1_ITEM_rptr(PKCS12_AUTHSAFES),
-        ossl_pkcs7_ctx_get0_libctx(p7ctx),
-        ossl_pkcs7_ctx_get0_propq(p7ctx));
+        ASN1_ITEM_rptr(PKCS12_AUTHSAFES), libctx, propq);
     if (p7s != NULL) {
         for (i = 0; i < sk_PKCS7_num(p7s); i++) {
             p7 = sk_PKCS7_value(p7s, i);
-            if (!ossl_pkcs7_ctx_propagate(p12->authsafes, p7))
+            ossl_pkcs7_set0_libctx(p7, libctx);
+            if (!ossl_pkcs7_set1_propq(p7, propq))
                 goto err;
+            ossl_pkcs7_resolve_libctx(p7);
         }
     }
     return p7s;
@@ -222,3 +258,11 @@ err:
     sk_PKCS7_pop_free(p7s, PKCS7_free);
     return NULL;
 }
+
+STACK_OF(PKCS7) *PKCS12_unpack_authsafes(const PKCS12 *p12)
+{
+    PKCS7_CTX *p7ctx;
+    p7ctx = &p12->authsafes->ctx;
+
+    return ossl_pkcs12_unpack_authsafes_ex(p12, ossl_pkcs7_ctx_get0_libctx(p7ctx), ossl_pkcs7_ctx_get0_propq(p7ctx));
+}
diff --git a/crypto/pkcs12/p12_crt.c b/crypto/pkcs12/p12_crt.c
index 948d46b851..c0bcc155af 100644
--- a/crypto/pkcs12/p12_crt.c
+++ b/crypto/pkcs12/p12_crt.c
@@ -126,7 +126,7 @@ PKCS12 *PKCS12_create_ex2(const char *pass, const char *name, EVP_PKEY *pkey,
         if (!copy_bag_attr(bag, pkey, NID_LocalKeySet))
             goto err;

-        if (name && !PKCS12_add_friendlyname(bag, name, -1))
+        if (name && !PKCS12_add_friendlyname(bag, name, namelen))
             goto err;
         if (keyidlen && !PKCS12_add_localkeyid(bag, keyid, keyidlen))
             goto err;
diff --git a/crypto/pkcs12/p12_kiss.c b/crypto/pkcs12/p12_kiss.c
index 20fd100cf3..55f2ca2b60 100644
--- a/crypto/pkcs12/p12_kiss.c
+++ b/crypto/pkcs12/p12_kiss.c
@@ -10,49 +10,92 @@
 #include <stdio.h>
 #include "internal/cryptlib.h"
 #include <openssl/pkcs12.h>
+#include "crypto/pkcs7/pk7_local.h"
+#include "p12_local.h"
 #include "crypto/x509.h" /* for ossl_x509_add_cert_new() */

 /* Simplified PKCS#12 routines */

 static int parse_pk12(PKCS12 *p12, const char *pass, int passlen,
-    EVP_PKEY **pkey, STACK_OF(X509) *ocerts);
+    PKCS12_PARSE_CTX *ctx,
+    OSSL_LIB_CTX *libctx, const char *propq);

 static int parse_bags(const STACK_OF(PKCS12_SAFEBAG) *bags, const char *pass,
-    int passlen, EVP_PKEY **pkey, STACK_OF(X509) *ocerts,
+    int passlen, PKCS12_PARSE_CTX *ctx,
     OSSL_LIB_CTX *libctx, const char *propq);

 static int parse_bag(PKCS12_SAFEBAG *bag, const char *pass, int passlen,
-    EVP_PKEY **pkey, STACK_OF(X509) *ocerts,
+    PKCS12_PARSE_CTX *ctx,
     OSSL_LIB_CTX *libctx, const char *propq);

+PKCS12_PARSE_CTX *PKCS12_PARSE_CTX_new(void)
+{
+    return OPENSSL_zalloc(sizeof(PKCS12_PARSE_CTX));
+}
+
+void PKCS12_PARSE_CTX_free(PKCS12_PARSE_CTX *ctx)
+{
+    OPENSSL_free(ctx);
+}
+
+void PKCS12_PARSE_CTX_set_pkey(PKCS12_PARSE_CTX *ctx, EVP_PKEY **pkey)
+{
+    ctx->pkey = pkey;
+}
+
+void PKCS12_PARSE_CTX_set_cert(PKCS12_PARSE_CTX *ctx, X509 **cert)
+{
+    ctx->cert = cert;
+}
+
+void PKCS12_PARSE_CTX_set_ca(PKCS12_PARSE_CTX *ctx, STACK_OF(X509) **ca)
+{
+    ctx->ca = ca;
+}
+
+void PKCS12_PARSE_CTX_set_skeys(PKCS12_PARSE_CTX *ctx, STACK_OF(EVP_SKEY) **skeys)
+{
+    ctx->skeys = skeys;
+}
+
 /*
- * Parse and decrypt a PKCS#12 structure returning user key, user cert and
- * other (CA) certs. Note either ca should be NULL, *ca should be NULL, or it
- * should point to a valid STACK structure. pkey and/or cert may be NULL;
- * if non-NULL the variables they point to can be passed uninitialised.
+ * Parse and decrypt a PKCS#12 structure returning user key, user cert,
+ * other (CA) certs, and/or symmetric secret keys according to ctx settings.
  */

-int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
-    STACK_OF(X509) **ca)
+int PKCS12_parse_ex(PKCS12 *p12, const char *pass,
+    PKCS12_PARSE_CTX *ctx,
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
-    STACK_OF(X509) *ocerts = NULL;
     X509 *x = NULL;
-    STACK_OF(X509) *initial_ca = ca != NULL ? *ca : NULL;
-    int initial_ca_count = initial_ca == NULL ? 0 : sk_X509_num(initial_ca);
+    STACK_OF(X509) *initial_ca = NULL;
+    STACK_OF(EVP_SKEY) *initial_skeys = NULL;
+    int initial_ca_count = 0;
+    int initial_skeys_count = 0;
     int ca_added_count = 0;
-
-    if (pkey != NULL)
-        *pkey = NULL;
-    if (cert != NULL)
-        *cert = NULL;
-
-    /* Check for NULL PKCS12 structure */
+    int skeys_added_count = 0;

     if (p12 == NULL) {
         ERR_raise(ERR_LIB_PKCS12, PKCS12_R_INVALID_NULL_PKCS12_POINTER);
         return 0;
     }

+    if (ctx == NULL) {
+        ERR_raise(ERR_LIB_PKCS12, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    initial_ca = ctx->ca != NULL ? *ctx->ca : NULL;
+    initial_ca_count = initial_ca == NULL ? 0 : sk_X509_num(initial_ca);
+    initial_skeys = ctx->skeys != NULL ? *ctx->skeys : NULL;
+    initial_skeys_count = initial_skeys == NULL ? 0 : sk_EVP_SKEY_num(initial_skeys);
+
+    if (ctx->pkey != NULL)
+        *ctx->pkey = NULL;
+    if (ctx->cert != NULL)
+        *ctx->cert = NULL;
+    ctx->ocerts = NULL;
+
     /* Check the mac */
     if (PKCS12_mac_present(p12)) {
         /*
@@ -62,15 +105,15 @@ int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
          * password are two different things...
          */
         if (pass == NULL || *pass == '\0') {
-            if (PKCS12_verify_mac(p12, NULL, 0))
+            if (ossl_pkcs12_verify_mac(p12, NULL, 0, libctx, propq))
                 pass = NULL;
-            else if (PKCS12_verify_mac(p12, "", 0))
+            else if (ossl_pkcs12_verify_mac(p12, "", 0, libctx, propq))
                 pass = "";
             else {
                 ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_VERIFY_FAILURE);
                 goto err;
             }
-        } else if (!PKCS12_verify_mac(p12, pass, -1)) {
+        } else if (!ossl_pkcs12_verify_mac(p12, pass, -1, libctx, propq)) {
             ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_VERIFY_FAILURE);
             goto err;
         }
@@ -79,13 +122,13 @@ int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
     }

     /* If needed, allocate stack for other certificates */
-    if ((cert != NULL || ca != NULL)
-        && (ocerts = sk_X509_new_null()) == NULL) {
+    if ((ctx->cert != NULL || ctx->ca != NULL)
+        && (ctx->ocerts = sk_X509_new_null()) == NULL) {
         ERR_raise(ERR_LIB_PKCS12, ERR_R_CRYPTO_LIB);
         goto err;
     }

-    if (!parse_pk12(p12, pass, -1, pkey, ocerts)) {
+    if (!parse_pk12(p12, pass, -1, ctx, libctx, propq)) {
         int err = ERR_peek_last_error();

         if (ERR_GET_LIB(err) != ERR_LIB_EVP
@@ -95,71 +138,112 @@ int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
     }

     /* Split the certs in ocerts over *cert and *ca as far as requested */
-    while ((x = sk_X509_shift(ocerts)) != NULL) {
-        if (pkey != NULL && *pkey != NULL
-            && cert != NULL && *cert == NULL) {
+    while ((x = sk_X509_shift(ctx->ocerts)) != NULL) {
+        if (ctx->pkey != NULL && *ctx->pkey != NULL
+            && ctx->cert != NULL && *ctx->cert == NULL) {
             int match;

             ERR_set_mark();
-            match = X509_check_private_key(x, *pkey);
+            match = X509_check_private_key(x, *ctx->pkey);
             ERR_pop_to_mark();
             if (match) {
-                *cert = x;
+                *ctx->cert = x;
                 continue;
             }
         }

-        if (ca != NULL) {
-            if (!ossl_x509_add_cert_new(ca, x, X509_ADD_FLAG_DEFAULT))
+        if (ctx->ca != NULL) {
+            if (!ossl_x509_add_cert_new(ctx->ca, x, X509_ADD_FLAG_DEFAULT))
                 goto err;
             continue;
         }
         X509_free(x);
     }
-    sk_X509_free(ocerts);
+    sk_X509_free(ctx->ocerts);
+    ctx->ocerts = NULL;

     return 1;

 err:
-
-    if (pkey != NULL) {
-        EVP_PKEY_free(*pkey);
-        *pkey = NULL;
+    if (ctx->pkey != NULL) {
+        EVP_PKEY_free(*ctx->pkey);
+        *ctx->pkey = NULL;
     }
-    if (cert != NULL) {
-        X509_free(*cert);
-        *cert = NULL;
+    if (ctx->cert != NULL) {
+        X509_free(*ctx->cert);
+        *ctx->cert = NULL;
+    }
+    skeys_added_count = ctx->skeys == NULL || *ctx->skeys == NULL ? 0 : sk_EVP_SKEY_num(*ctx->skeys) - initial_skeys_count;
+    while (skeys_added_count > 0) {
+        EVP_SKEY *t_skey = sk_EVP_SKEY_pop(*ctx->skeys);
+        EVP_SKEY_free(t_skey);
+        skeys_added_count--;
+    }
+
+    if (initial_skeys == NULL && ctx->skeys != NULL) {
+        sk_EVP_SKEY_free(*ctx->skeys);
+        *ctx->skeys = NULL;
     }

-    ca_added_count = ca == NULL || *ca == NULL ? 0 : sk_X509_num(*ca) - initial_ca_count;
+    ca_added_count = ctx->ca == NULL || *ctx->ca == NULL ? 0 : sk_X509_num(*ctx->ca) - initial_ca_count;
     while (ca_added_count > 0) {
-        X509 *t_cert = sk_X509_pop(*ca);
+        X509 *t_cert = sk_X509_pop(*ctx->ca);
         X509_free(t_cert);
         ca_added_count--;
     }

-    if (initial_ca == NULL && ca != NULL) {
-        sk_X509_free(*ca);
-        *ca = NULL;
+    if (initial_ca == NULL && ctx->ca != NULL) {
+        sk_X509_free(*ctx->ca);
+        *ctx->ca = NULL;
     }

     X509_free(x);
-    OSSL_STACK_OF_X509_free(ocerts);
+    OSSL_STACK_OF_X509_free(ctx->ocerts);
+    ctx->ocerts = NULL;
     return 0;
 }

+int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
+    STACK_OF(X509) **ca)
+{
+    PKCS12_PARSE_CTX *ctx;
+    int ret;
+
+    if (pkey != NULL)
+        *pkey = NULL;
+    if (cert != NULL)
+        *cert = NULL;
+
+    if (p12 == NULL) {
+        ERR_raise(ERR_LIB_PKCS12, PKCS12_R_INVALID_NULL_PKCS12_POINTER);
+        return 0;
+    }
+
+    ctx = PKCS12_PARSE_CTX_new();
+    if (ctx == NULL)
+        return 0;
+
+    PKCS12_PARSE_CTX_set_pkey(ctx, pkey);
+    PKCS12_PARSE_CTX_set_cert(ctx, cert);
+    PKCS12_PARSE_CTX_set_ca(ctx, ca);
+
+    ret = PKCS12_parse_ex(p12, pass, ctx, p12->authsafes->ctx.libctx, p12->authsafes->ctx.propq);
+    PKCS12_PARSE_CTX_free(ctx);
+    return ret;
+}
+
 /* Parse the outer PKCS#12 structure */

-/* pkey and/or ocerts may be NULL */
 static int parse_pk12(PKCS12 *p12, const char *pass, int passlen,
-    EVP_PKEY **pkey, STACK_OF(X509) *ocerts)
+    PKCS12_PARSE_CTX *ctx,
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
     STACK_OF(PKCS7) *asafes;
     STACK_OF(PKCS12_SAFEBAG) *bags;
     int i, bagnid;
     PKCS7 *p7;

-    if ((asafes = PKCS12_unpack_authsafes(p12)) == NULL)
+    if ((asafes = ossl_pkcs12_unpack_authsafes_ex(p12, libctx, propq)) == NULL)
         return 0;
     for (i = 0; i < sk_PKCS7_num(asafes); i++) {
         p7 = sk_PKCS7_value(asafes, i);
@@ -167,15 +251,15 @@ static int parse_pk12(PKCS12 *p12, const char *pass, int passlen,
         if (bagnid == NID_pkcs7_data) {
             bags = PKCS12_unpack_p7data(p7);
         } else if (bagnid == NID_pkcs7_encrypted) {
-            bags = PKCS12_unpack_p7encdata(p7, pass, passlen);
+            bags = ossl_pkcs12_unpack_p7encdata_ex(p7, pass, passlen,
+                libctx, propq);
         } else
             continue;
         if (!bags) {
             sk_PKCS7_pop_free(asafes, PKCS7_free);
             return 0;
         }
-        if (!parse_bags(bags, pass, passlen, pkey, ocerts,
-                p7->ctx.libctx, p7->ctx.propq)) {
+        if (!parse_bags(bags, pass, passlen, ctx, libctx, propq)) {
             sk_PKCS12_SAFEBAG_pop_free(bags, PKCS12_SAFEBAG_free);
             sk_PKCS7_pop_free(asafes, PKCS7_free);
             return 0;
@@ -186,24 +270,22 @@ static int parse_pk12(PKCS12 *p12, const char *pass, int passlen,
     return 1;
 }

-/* pkey and/or ocerts may be NULL */
 static int parse_bags(const STACK_OF(PKCS12_SAFEBAG) *bags, const char *pass,
-    int passlen, EVP_PKEY **pkey, STACK_OF(X509) *ocerts,
+    int passlen, PKCS12_PARSE_CTX *ctx,
     OSSL_LIB_CTX *libctx, const char *propq)
 {
     int i;
     for (i = 0; i < sk_PKCS12_SAFEBAG_num(bags); i++) {
         if (!parse_bag(sk_PKCS12_SAFEBAG_value(bags, i),
-                pass, passlen, pkey, ocerts,
+                pass, passlen, ctx,
                 libctx, propq))
             return 0;
     }
     return 1;
 }

-/* pkey and/or ocerts may be NULL */
 static int parse_bag(PKCS12_SAFEBAG *bag, const char *pass, int passlen,
-    EVP_PKEY **pkey, STACK_OF(X509) *ocerts,
+    PKCS12_PARSE_CTX *ctx,
     OSSL_LIB_CTX *libctx, const char *propq)
 {
     PKCS8_PRIV_KEY_INFO *p8;
@@ -226,29 +308,29 @@ static int parse_bag(PKCS12_SAFEBAG *bag, const char *pass, int passlen,

     switch (PKCS12_SAFEBAG_get_nid(bag)) {
     case NID_keyBag:
-        if (pkey == NULL || *pkey != NULL)
+        if (ctx->pkey == NULL || *ctx->pkey != NULL)
             return 1;
-        *pkey = EVP_PKCS82PKEY_ex(PKCS12_SAFEBAG_get0_p8inf(bag),
+        *ctx->pkey = EVP_PKCS82PKEY_ex(PKCS12_SAFEBAG_get0_p8inf(bag),
             libctx, propq);
-        if (*pkey == NULL)
+        if (*ctx->pkey == NULL)
             return 0;
         break;

     case NID_pkcs8ShroudedKeyBag:
-        if (pkey == NULL || *pkey != NULL)
+        if (ctx->pkey == NULL || *ctx->pkey != NULL)
             return 1;
         if ((p8 = PKCS12_decrypt_skey_ex(bag, pass, passlen,
                  libctx, propq))
             == NULL)
             return 0;
-        *pkey = EVP_PKCS82PKEY_ex(p8, libctx, propq);
+        *ctx->pkey = EVP_PKCS82PKEY_ex(p8, libctx, propq);
         PKCS8_PRIV_KEY_INFO_free(p8);
-        if (!(*pkey))
+        if (!(*ctx->pkey))
             return 0;
         break;

     case NID_certBag:
-        if (ocerts == NULL
+        if (ctx->ocerts == NULL
             || PKCS12_SAFEBAG_get_bag_nid(bag) != NID_x509Certificate)
             return 1;
         if ((x509 = PKCS12_SAFEBAG_get1_cert_ex(bag, libctx, propq)) == NULL)
@@ -272,16 +354,45 @@ static int parse_bag(PKCS12_SAFEBAG *bag, const char *pass, int passlen,
             }
         }

-        if (!sk_X509_push(ocerts, x509)) {
+        if (!sk_X509_push(ctx->ocerts, x509)) {
             X509_free(x509);
             return 0;
         }

         break;

+    case NID_secretBag:
+        if (ctx->skeys == NULL)
+            return 1;
+        if (PKCS12_SAFEBAG_get_bag_nid(bag) != NID_pkcs8ShroudedKeyBag)
+            return 1;
+        {
+            EVP_SKEY *skey;
+            PKCS8_PRIV_KEY_INFO *p8sb = PKCS12_decrypt_secretbag(bag, pass,
+                passlen, libctx, propq);
+            if (p8sb == NULL)
+                return 0;
+            skey = PKCS8_PRIV_KEY_INFO_get1_skey(p8sb, libctx, propq);
+            PKCS8_PRIV_KEY_INFO_free(p8sb);
+            if (skey == NULL) {
+                ERR_raise(ERR_LIB_PKCS12, PKCS12_R_PARSE_ERROR);
+                return 0;
+            }
+            if (*ctx->skeys == NULL
+                && (*ctx->skeys = sk_EVP_SKEY_new_null()) == NULL) {
+                EVP_SKEY_free(skey);
+                return 0;
+            }
+            if (!sk_EVP_SKEY_push(*ctx->skeys, skey)) {
+                EVP_SKEY_free(skey);
+                return 0;
+            }
+        }
+        return 1;
+
     case NID_safeContentsBag:
-        return parse_bags(PKCS12_SAFEBAG_get0_safes(bag), pass, passlen, pkey,
-            ocerts, libctx, propq);
+        return parse_bags(PKCS12_SAFEBAG_get0_safes(bag), pass, passlen, ctx,
+            libctx, propq);

     default:
         return 1;
diff --git a/crypto/pkcs12/p12_local.h b/crypto/pkcs12/p12_local.h
index 4025fbab16..147454bc7a 100644
--- a/crypto/pkcs12/p12_local.h
+++ b/crypto/pkcs12/p12_local.h
@@ -49,6 +49,22 @@ struct pkcs12_bag_st {
     } value;
 };

+struct pkcs12_parse_ctx_st {
+    EVP_PKEY **pkey;
+    X509 **cert;
+    STACK_OF(X509) **ca;
+    STACK_OF(EVP_SKEY) **skeys;
+    /* internal: temporary cert collection used during parsing */
+    STACK_OF(X509) *ocerts;
+};
+
 const PKCS7_CTX *ossl_pkcs12_get0_pkcs7ctx(const PKCS12 *p12);
+int ossl_pkcs12_verify_mac(PKCS12 *p12, const char *pass, int passlen,
+    OSSL_LIB_CTX *libctx, const char *propq);
+STACK_OF(PKCS7) *ossl_pkcs12_unpack_authsafes_ex(const PKCS12 *p12,
+    OSSL_LIB_CTX *libctx, const char *propq);
+STACK_OF(PKCS12_SAFEBAG) *ossl_pkcs12_unpack_p7encdata_ex(PKCS7 *p7,
+    const char *pass, int passlen,
+    OSSL_LIB_CTX *libctx, const char *propq);

 #endif /* !defined(OSSL_LIBCRYPTO_PKCS12_P12_LOCAL_H) */
diff --git a/crypto/pkcs12/p12_mutl.c b/crypto/pkcs12/p12_mutl.c
index 6720349d8d..f434b15f63 100644
--- a/crypto/pkcs12/p12_mutl.c
+++ b/crypto/pkcs12/p12_mutl.c
@@ -182,7 +182,8 @@ static int pkcs12_gen_mac(PKCS12 *p12, const char *pass, int passlen,
         unsigned char *out,
         const EVP_MD *md_type,
         OSSL_LIB_CTX *libctx,
-        const char *propq))
+        const char *propq),
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
     int ret = 0;
     EVP_MD *md;
@@ -193,8 +194,6 @@ static int pkcs12_gen_mac(PKCS12 *p12, const char *pass, int passlen,
     int md_nid = NID_undef;
     const X509_ALGOR *macalg;
     const ASN1_OBJECT *macoid;
-    OSSL_LIB_CTX *libctx;
-    const char *propq;
     size_t md_sz, outlen;

     if (!PKCS7_type_is_data(p12->authsafes)) {
@@ -207,8 +206,6 @@ static int pkcs12_gen_mac(PKCS12 *p12, const char *pass, int passlen,
         return 0;
     }

-    libctx = p12->authsafes->ctx.libctx;
-    propq = p12->authsafes->ctx.propq;
     salt = p12->mac->salt->data;
     saltlen = p12->mac->salt->length;
     if (p12->mac->iter == NULL)
@@ -305,11 +302,11 @@ err:
 int PKCS12_gen_mac(PKCS12 *p12, const char *pass, int passlen,
     unsigned char *mac, unsigned int *maclen)
 {
-    return pkcs12_gen_mac(p12, pass, passlen, mac, maclen, NID_undef, NID_undef, NULL);
+    return pkcs12_gen_mac(p12, pass, passlen, mac, maclen, NID_undef, NID_undef, NULL, p12->authsafes->ctx.libctx, p12->authsafes->ctx.propq);
 }

-/* Verify the mac */
-int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)
+int ossl_pkcs12_verify_mac(PKCS12 *p12, const char *pass, int passlen,
+    OSSL_LIB_CTX *libctx, const char *propq)
 {
     unsigned char mac[EVP_MAX_MD_SIZE];
     unsigned int maclen;
@@ -337,14 +334,14 @@ int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)
         X509_ALGOR_get0(&hmac_oid, NULL, NULL, param->messageAuthScheme);
         md_nid = ossl_hmac2mdnid(OBJ_obj2nid(hmac_oid));

-        if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, md_nid, NID_undef, NULL)) {
+        if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, md_nid, NID_undef, NULL, libctx, propq)) {
             ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_GENERATION_ERROR);
             PBMAC1PARAM_free(param);
             return 0;
         }
         PBMAC1PARAM_free(param);
     } else {
-        if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, NID_undef, NID_undef, NULL)) {
+        if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, NID_undef, NID_undef, NULL, libctx, propq)) {
             ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_GENERATION_ERROR);
             return 0;
         }
@@ -357,6 +354,12 @@ int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)
     return 1;
 }

+/* Verify the mac */
+int PKCS12_verify_mac(PKCS12 *p12, const char *pass, int passlen)
+{
+    return ossl_pkcs12_verify_mac(p12, pass, passlen, p12->authsafes->ctx.libctx, p12->authsafes->ctx.propq);
+}
+
 /* Set a mac */
 int PKCS12_set_mac(PKCS12 *p12, const char *pass, int passlen,
     unsigned char *salt, int saltlen, int iter,
@@ -378,7 +381,7 @@ int PKCS12_set_mac(PKCS12 *p12, const char *pass, int passlen,
     /*
      * Note that output mac is forced to UTF-8...
      */
-    if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, NID_undef, NID_undef, NULL)) {
+    if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen, NID_undef, NID_undef, NULL, p12->authsafes->ctx.libctx, p12->authsafes->ctx.propq)) {
         ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_GENERATION_ERROR);
         return 0;
     }
@@ -542,7 +545,7 @@ int PKCS12_set_pbmac1_pbkdf2(PKCS12 *p12, const char *pass, int passlen,
      */
     if (!pkcs12_gen_mac(p12, pass, passlen, mac, &maclen,
             EVP_MD_get_type(md_type), prf_md_nid,
-            pkcs12_pbmac1_pbkdf2_key_gen)) {
+            pkcs12_pbmac1_pbkdf2_key_gen, p12->authsafes->ctx.libctx, p12->authsafes->ctx.propq)) {
         ERR_raise(ERR_LIB_PKCS12, PKCS12_R_MAC_GENERATION_ERROR);
         goto err;
     }
diff --git a/crypto/pkcs12/p12_npas.c b/crypto/pkcs12/p12_npas.c
index 44763e8668..6b25bf95bd 100644
--- a/crypto/pkcs12/p12_npas.c
+++ b/crypto/pkcs12/p12_npas.c
@@ -99,7 +99,9 @@ static int newpass_p12(PKCS12 *p12, const char *oldpass, const char *newpass)
         if (bagnid == NID_pkcs7_data)
             p7new = PKCS12_pack_p7data(bags);
         else
-            p7new = PKCS12_pack_p7encdata_ex(pbe_nid, newpass, -1, NULL,
+            p7new = PKCS12_pack_p7encdata_ex(
+                cipherid != NID_undef ? cipherid : pbe_nid,
+                newpass, -1, NULL,
                 pbe_saltlen, pbe_iter, bags,
                 p7->ctx.libctx, p7->ctx.propq);
         if (p7new == NULL || !sk_PKCS7_push(newsafes, p7new)) {
diff --git a/crypto/pkcs12/p12_sbag.c b/crypto/pkcs12/p12_sbag.c
index 2bb7c6fa8b..40f53a71fa 100644
--- a/crypto/pkcs12/p12_sbag.c
+++ b/crypto/pkcs12/p12_sbag.c
@@ -10,6 +10,8 @@
 #include <stdio.h>
 #include "internal/cryptlib.h"
 #include <openssl/pkcs12.h>
+#include <openssl/core_names.h>
+#include <openssl/objects.h>
 #include "p12_local.h"
 #include "crypto/x509.h"

@@ -147,6 +149,63 @@ X509_CRL *PKCS12_SAFEBAG_get1_crl_ex(const PKCS12_SAFEBAG *bag,
     return ret;
 }

+EVP_SKEY *PKCS8_PRIV_KEY_INFO_get1_skey(const PKCS8_PRIV_KEY_INFO *p8inf,
+    OSSL_LIB_CTX *libctx, const char *propq)
+{
+    const ASN1_OBJECT *algoid = NULL;
+    const unsigned char *raw_key = NULL;
+    int raw_key_len = 0;
+    const char *skey_type = OSSL_SKEY_TYPE_GENERIC;
+
+    if (p8inf == NULL)
+        return NULL;
+
+    if (!PKCS8_pkey_get0(&algoid, &raw_key, &raw_key_len, NULL, p8inf)
+        || raw_key == NULL || raw_key_len <= 0)
+        return NULL;
+
+    if (algoid != NULL) {
+        int nid = OBJ_obj2nid(algoid);
+
+        switch (nid) {
+        case NID_id_aes:
+            if (raw_key_len != 16 && raw_key_len != 24 && raw_key_len != 32)
+                return NULL;
+            skey_type = OSSL_SKEY_TYPE_AES;
+            break;
+        case NID_aes_128_cbc:
+        case NID_aes_128_ccm:
+        case NID_aes_128_ecb:
+        case NID_aes_128_gcm:
+            if (raw_key_len != 16)
+                return NULL;
+            skey_type = OSSL_SKEY_TYPE_AES;
+            break;
+        case NID_aes_192_cbc:
+        case NID_aes_192_ccm:
+        case NID_aes_192_ecb:
+        case NID_aes_192_gcm:
+            if (raw_key_len != 24)
+                return NULL;
+            skey_type = OSSL_SKEY_TYPE_AES;
+            break;
+        case NID_aes_256_cbc:
+        case NID_aes_256_ccm:
+        case NID_aes_256_ecb:
+        case NID_aes_256_gcm:
+            if (raw_key_len != 32)
+                return NULL;
+            skey_type = OSSL_SKEY_TYPE_AES;
+            break;
+        default:
+            break;
+        }
+    }
+
+    return EVP_SKEY_import_raw_key(libctx, skey_type,
+        (unsigned char *)raw_key, raw_key_len, propq);
+}
+
 PKCS12_SAFEBAG *PKCS12_SAFEBAG_create_cert(X509 *x509)
 {
     return PKCS12_item_pack_safebag(x509, ASN1_ITEM_rptr(X509),
diff --git a/fuzz/pkcs12.c b/fuzz/pkcs12.c
index 74e2b6dd8a..b8c8927253 100644
--- a/fuzz/pkcs12.c
+++ b/fuzz/pkcs12.c
@@ -27,14 +27,31 @@ int FuzzerInitialize(int *argc, char ***argv)
     return 1;
 }

-int FuzzerTestOneInput(const uint8_t *buf, size_t len)
+static void fuzz_pkcs12_parse(PKCS12 *p12, const char *pass)
 {
-    PKCS12 *p12;
-    BIO *in;
+    PKCS12_PARSE_CTX *ctx;
     EVP_PKEY *pkey = NULL;
     X509 *cert = NULL;
     STACK_OF(X509) *ca = NULL;

+    ctx = PKCS12_PARSE_CTX_new();
+    if (ctx == NULL)
+        return;
+    PKCS12_PARSE_CTX_set_pkey(ctx, &pkey);
+    PKCS12_PARSE_CTX_set_cert(ctx, &cert);
+    PKCS12_PARSE_CTX_set_ca(ctx, &ca);
+    PKCS12_parse_ex(p12, pass, ctx, NULL, NULL);
+    PKCS12_PARSE_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+    X509_free(cert);
+    OSSL_STACK_OF_X509_free(ca);
+}
+
+int FuzzerTestOneInput(const uint8_t *buf, size_t len)
+{
+    PKCS12 *p12;
+    BIO *in;
+
     if (len == 0 || len > INT_MAX)
         return 0;

@@ -53,18 +70,8 @@ int FuzzerTestOneInput(const uint8_t *buf, size_t len)
         PKCS12_verify_mac(p12, NULL, 0);
         PKCS12_verify_mac(p12, "", 0);

-        PKCS12_parse(p12, NULL, &pkey, &cert, &ca);
-        EVP_PKEY_free(pkey);
-        X509_free(cert);
-        OSSL_STACK_OF_X509_free(ca);
-
-        pkey = NULL;
-        cert = NULL;
-        ca = NULL;
-        PKCS12_parse(p12, "", &pkey, &cert, &ca);
-        EVP_PKEY_free(pkey);
-        X509_free(cert);
-        OSSL_STACK_OF_X509_free(ca);
+        fuzz_pkcs12_parse(p12, NULL);
+        fuzz_pkcs12_parse(p12, "");

         PKCS12_free(p12);
     }
diff --git a/include/openssl/evp.h b/include/openssl/evp.h.in
similarity index 99%
rename from include/openssl/evp.h
rename to include/openssl/evp.h.in
index 55503411eb..7bad41ba79 100644
--- a/include/openssl/evp.h
+++ b/include/openssl/evp.h.in
@@ -7,6 +7,12 @@
  * https://www.openssl.org/source/license.html
  */

+/* clang-format off */
+{-
+use OpenSSL::stackhash qw(generate_stack_macros);
+-}
+/* clang-format on */
+
 #ifndef OPENSSL_EVP_H
 #define OPENSSL_EVP_H
 #pragma once
@@ -118,6 +124,12 @@
 extern "C" {
 #endif

+/* clang-format off */
+{-
+    generate_stack_macros("EVP_SKEY");
+-}
+/* clang-format on */
+
 int EVP_set_default_properties(OSSL_LIB_CTX *libctx, const char *propq);
 char *EVP_get1_default_properties(OSSL_LIB_CTX *libctx);
 int EVP_default_properties_is_fips_enabled(OSSL_LIB_CTX *libctx);
diff --git a/include/openssl/pkcs12.h.in b/include/openssl/pkcs12.h.in
index 6555322be3..e966228ea9 100644
--- a/include/openssl/pkcs12.h.in
+++ b/include/openssl/pkcs12.h.in
@@ -27,6 +27,7 @@ use OpenSSL::stackhash qw(generate_stack_macros);
 #include <openssl/bio.h>
 #include <openssl/core.h>
 #include <openssl/x509.h>
+#include <openssl/evp.h>
 #include <openssl/pkcs12err.h>
 #ifndef OPENSSL_NO_STDIO
 #include <stdio.h>
@@ -125,6 +126,8 @@ X509 *PKCS12_SAFEBAG_get1_cert_ex(const PKCS12_SAFEBAG *bag, OSSL_LIB_CTX *libct
 X509 *PKCS12_SAFEBAG_get1_cert(const PKCS12_SAFEBAG *bag);
 X509_CRL *PKCS12_SAFEBAG_get1_crl_ex(const PKCS12_SAFEBAG *bag, OSSL_LIB_CTX *libctx, const char *propq);
 X509_CRL *PKCS12_SAFEBAG_get1_crl(const PKCS12_SAFEBAG *bag);
+EVP_SKEY *PKCS8_PRIV_KEY_INFO_get1_skey(const PKCS8_PRIV_KEY_INFO *p8inf,
+    OSSL_LIB_CTX *libctx, const char *propq);
 const STACK_OF(PKCS12_SAFEBAG) *
 PKCS12_SAFEBAG_get0_safes(const PKCS12_SAFEBAG *bag);
 const PKCS8_PRIV_KEY_INFO *PKCS12_SAFEBAG_get0_p8inf(const PKCS12_SAFEBAG *bag);
@@ -163,6 +166,9 @@ PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_skey_ex(const PKCS12_SAFEBAG *bag,
     const char *pass, int passlen,
     OSSL_LIB_CTX *ctx,
     const char *propq);
+PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_secretbag(const PKCS12_SAFEBAG *bag,
+    const char *pass, int passlen,
+    OSSL_LIB_CTX *ctx, const char *propq);
 X509_SIG *PKCS8_encrypt(int pbe_nid, const EVP_CIPHER *cipher,
     const char *pass, int passlen, unsigned char *salt,
     int saltlen, int iter, PKCS8_PRIV_KEY_INFO *p8);
@@ -300,6 +306,19 @@ DECLARE_ASN1_ITEM(PKCS12_SAFEBAGS)
 DECLARE_ASN1_ITEM(PKCS12_AUTHSAFES)

 void PKCS12_PBE_add(void);
+
+typedef struct pkcs12_parse_ctx_st PKCS12_PARSE_CTX;
+PKCS12_PARSE_CTX *PKCS12_PARSE_CTX_new(void);
+void PKCS12_PARSE_CTX_free(PKCS12_PARSE_CTX *ctx);
+void PKCS12_PARSE_CTX_set_pkey(PKCS12_PARSE_CTX *ctx, EVP_PKEY **pkey);
+void PKCS12_PARSE_CTX_set_cert(PKCS12_PARSE_CTX *ctx, X509 **cert);
+void PKCS12_PARSE_CTX_set_ca(PKCS12_PARSE_CTX *ctx, STACK_OF(X509) **ca);
+void PKCS12_PARSE_CTX_set_skeys(PKCS12_PARSE_CTX *ctx, STACK_OF(EVP_SKEY) **skeys);
+
+int PKCS12_parse_ex(PKCS12 *p12, const char *pass,
+    PKCS12_PARSE_CTX *ctx,
+    OSSL_LIB_CTX *libctx, const char *propq);
+
 int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
     STACK_OF(X509) **ca);
 typedef int PKCS12_create_cb(PKCS12_SAFEBAG *bag, void *cbarg);
diff --git a/test/recipes/80-test_pkcs12.t b/test/recipes/80-test_pkcs12.t
index e1457639c4..862a81abbc 100644
--- a/test/recipes/80-test_pkcs12.t
+++ b/test/recipes/80-test_pkcs12.t
@@ -520,7 +520,7 @@ unless ($no_fips) {
                                 stderr => "outerr.txt"),
                                 capture => 1);
                     open DATA, "outerr.txt";
-                    my @match = grep /:PKCS12_parse:parse error:/, <DATA>;
+                    my @match = grep /:PKCS12_parse_ex:parse error:/, <DATA>;
                     close DATA;
                     ok(scalar @match > 0, "Test against CVE-2026-22795 , missing ASN1_TYPE validation in cert, sha256mac");
                     }
@@ -544,7 +544,7 @@ unless ($no_fips) {
                             stderr => "outerr.txt"),
                             capture => 1);
                 open DATA, "outerr.txt";
-                my @match = grep /:PKCS12_parse:parse error:/, <DATA>;
+                my @match = grep /:PKCS12_parse_ex:parse error:/, <DATA>;
                 close DATA;
                 ok(scalar @match > 0, "Test against CVE-2026-22795 , missing ASN1_TYPE validation in cert, pbmac1");
             }
@@ -569,7 +569,7 @@ unless ($no_fips) {
                                 stderr => "outerr.txt"),
                                 capture => 1);
                     open DATA, "outerr.txt";
-                    my @match = grep /:PKCS12_parse:parse error:/, <DATA>;
+                    my @match = grep /:PKCS12_parse_ex:parse error:/, <DATA>;
                     close DATA;
                     ok(scalar @match > 0, "Test against CVE-2026-22795 , missing ASN1_TYPE validation in keys, sha256mac");
                 }
@@ -594,7 +594,7 @@ unless ($no_fips) {
                             stderr => "outerr.txt"),
                             capture => 1);
                 open DATA, "outerr.txt";
-                my @match = grep /:PKCS12_parse:parse error:/, <DATA>;
+                my @match = grep /:PKCS12_parse_ex:parse error:/, <DATA>;
                 close DATA;
                 ok(scalar @match > 0, "Test against CVE-2026-22795 , missing ASN1_TYPE validation in keys, pbmac1");
             }
diff --git a/util/libcrypto.num b/util/libcrypto.num
index fe2d6afb4f..eebc52128d 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5738,3 +5738,12 @@ i2d_IPAddrBlocks                        ?	4_1_0	EXIST::FUNCTION:RFC3779
 IPAddrBlocks_free                       ?	4_1_0	EXIST::FUNCTION:RFC3779
 IPAddrBlocks_new                        ?	4_1_0	EXIST::FUNCTION:RFC3779
 IPAddrBlocks_it                         ?	4_1_0	EXIST::FUNCTION:RFC3779
+PKCS8_PRIV_KEY_INFO_get1_skey           ?	4_2_0	EXIST::FUNCTION:
+PKCS12_decrypt_secretbag                ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_new                    ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_free                   ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_set_pkey               ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_set_cert               ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_set_ca                 ?	4_2_0	EXIST::FUNCTION:
+PKCS12_PARSE_CTX_set_skeys              ?	4_2_0	EXIST::FUNCTION:
+PKCS12_parse_ex                         ?	4_2_0	EXIST::FUNCTION: