Commit c8af5fc081 for openssl.org

commit c8af5fc081e8914898c572ff1878c333ee7d4c8a
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date:   Thu Sep 10 17:43:01 2026 +0200

    Add symmetric key support to OSSL_STORE PKCS#12 loader

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

diff --git a/crypto/store/store_result.c b/crypto/store/store_result.c
index 576e3d307e..0a0198ee32 100644
--- a/crypto/store/store_result.c
+++ b/crypto/store/store_result.c
@@ -566,9 +566,11 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
     /* There is no specific object type for PKCS12 */
     if (data->object_type == OSSL_OBJECT_UNKNOWN) {
         /* Initial parsing */
-        PKCS12 *p12;
+        PKCS12 *p12 = PKCS12_init_ex(NID_pkcs7_data, libctx, propq);
+        if (p12 == NULL)
+            return 0;

-        p12 = d2i_PKCS12(NULL, (const unsigned char **)&data->octet_data,
+        p12 = d2i_PKCS12(&p12, (const unsigned char **)&data->octet_data,
             (long)data->octet_data_size);

         if (p12 != NULL) {
@@ -578,6 +580,8 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
             EVP_PKEY *pkey = NULL;
             X509 *cert = NULL;
             STACK_OF(X509) *chain = NULL;
+            STACK_OF(EVP_SKEY) *skeys = NULL;
+            PKCS12_PARSE_CTX *pctx = NULL;

             data->object_type = OSSL_OBJECT_PKCS12;

@@ -607,7 +611,7 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
                 pass = tpass;
                 /*
                  * ossl_pw_get_passphrase() does not NUL terminate but
-                 * we must do it for PKCS12_parse()
+                 * we must do it for PKCS12_parse_ex()
                  */
                 pass[tpass_len] = '\0';
                 if (!PKCS12_verify_mac(p12, pass, (int)tpass_len)) {
@@ -618,11 +622,19 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
                 }
             }

-            if (PKCS12_parse(p12, pass, &pkey, &cert, &chain)) {
+            if ((pctx = PKCS12_PARSE_CTX_new()) == NULL)
+                goto p12_end;
+            PKCS12_PARSE_CTX_set_pkey(pctx, &pkey);
+            PKCS12_PARSE_CTX_set_cert(pctx, &cert);
+            PKCS12_PARSE_CTX_set_ca(pctx, &chain);
+            PKCS12_PARSE_CTX_set_skeys(pctx, &skeys);
+
+            if (PKCS12_parse_ex(p12, pass, pctx, libctx, propq)) {
                 STACK_OF(OSSL_STORE_INFO) *infos = NULL;
                 OSSL_STORE_INFO *osi_pkey = NULL;
                 OSSL_STORE_INFO *osi_cert = NULL;
                 OSSL_STORE_INFO *osi_ca = NULL;
+                OSSL_STORE_INFO *osi_skey = NULL;

                 ok = 1; /* Parsing went through correctly! */

@@ -645,6 +657,16 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
                         else
                             ok = 0;
                     }
+                    while (ok && sk_EVP_SKEY_num(skeys) > 0) {
+                        EVP_SKEY *sk = sk_EVP_SKEY_value(skeys, 0);
+
+                        if ((osi_skey = OSSL_STORE_INFO_new_SKEY(sk)) != NULL
+                            && sk_EVP_SKEY_shift(skeys) != NULL
+                            && sk_OSSL_STORE_INFO_push(infos, osi_skey) != 0)
+                            osi_skey = NULL;
+                        else
+                            ok = 0;
+                    }
                     while (ok && sk_X509_num(chain) > 0) {
                         X509 *ca = sk_X509_value(chain, 0);

@@ -659,15 +681,18 @@ static int try_pkcs12(struct extracted_param_data_st *data, OSSL_STORE_INFO **v,
                 EVP_PKEY_free(pkey);
                 X509_free(cert);
                 OSSL_STACK_OF_X509_free(chain);
+                sk_EVP_SKEY_pop_free(skeys, EVP_SKEY_free);
                 OSSL_STORE_INFO_free(osi_pkey);
                 OSSL_STORE_INFO_free(osi_cert);
                 OSSL_STORE_INFO_free(osi_ca);
+                OSSL_STORE_INFO_free(osi_skey);
                 if (!ok) {
                     sk_OSSL_STORE_INFO_pop_free(infos, OSSL_STORE_INFO_free);
                     infos = NULL;
                 }
                 ctx->cached_info = infos;
             }
+            PKCS12_PARSE_CTX_free(pctx);
         p12_end:
             OPENSSL_cleanse(tpass, sizeof(tpass));
             PKCS12_free(p12);
diff --git a/test/recipes/80-test_pkcs12.t b/test/recipes/80-test_pkcs12.t
index 55afbd259c..a9015c69ee 100644
--- a/test/recipes/80-test_pkcs12.t
+++ b/test/recipes/80-test_pkcs12.t
@@ -57,7 +57,7 @@ $ENV{OPENSSL_WIN32_UTF8}=1;
 my $no_fips = disabled('fips') || ($ENV{NO_FIPS} // 0);
 my $no_err =  disabled('err') || disabled('autoerrinit');

-plan tests => 70 + ($no_fips ? 0 : 5);
+plan tests => 77 + ($no_fips ? 0 : 5);

 # Test different PKCS#12 formats
 ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats");
@@ -85,6 +85,7 @@ my $outfile4 = "out4.p12";
 my $outfile5 = "out5.p12";
 my $outfile6 = "out6.p12";
 my $outfile7 = "out7.p12";
+my $outfile8 = "out8.p12";

 # Test the -chain option with -untrusted
 ok(run(app(["openssl", "pkcs12", "-export", "-chain",
@@ -177,6 +178,15 @@ ok(grep(/Trusted key usage (Oracle)/, @pkcs12info) == 0,
     ok(scalar @match > 0 ? 0 : 1, "test_export_pkcs12_outerr6_empty");
 }

+# Export key + cert + distinct CA cert for combination tests
+ok(run(app(["openssl", "pkcs12", "-export",
+            "-inkey", srctop_file(@path, "ee-key.pem"),
+            "-in", srctop_file(@path, "ee-cert.pem"),
+            "-certfile", srctop_file(@path, "ca-cert.pem"),
+            "-passout", "pass:",
+            "-nomac", "-out", $outfile8])),
+   "test_export_pkcs12_key_cert_ca");
+
 # Test dumping a PKCS#12 file whose private key is stored in an unencrypted
 # keyBag (created with -keypbe NONE) rather than a shrouded keyBag.
 {
@@ -467,6 +477,13 @@ ok(run(test(["pkcs12_api_test",
                                 "mismatched_key_pass.p12"),
              "-mismatched-key-pass",
              ])), "Test PKCS12_parse with mismatched key password");
+ok(run(test(["pkcs12_api_test",
+             "-in", $outfile8,
+             "-pass", "",
+             "-has-ca", 1,
+             "-has-key", 1,
+             "-has-cert", 1,
+             ])), "Test pkcs12_parse() key+cert+ca combinations");

 # Test against CVE-2025-69421, octet parameter is expected, but
 # NULL is being received and dereferenced
@@ -621,4 +638,37 @@ ok(run(test(["pkcs12_api_test",
              "-num-skeys", "1",
              ])), "Test PKCS12_parse_ex() with symmetric key");

+# Test OSSL_STORE with Java symmetric key file
+{
+    my @output = run(app(["openssl", "storeutl",
+                         "-passin", "pass:password",
+                         srctop_file("test", "recipes", "80-test_pkcs12_data", "java-skey.p12")]),
+                    capture => 1);
+    ok(@output > 0, "Test OSSL_STORE loads symmetric key from PKCS#12");
+
+    my $output_text = join("", @output);
+    like($output_text, qr/Symmetric key/, "OSSL_STORE output shows symmetric key");
+}
+
+
+# Test PKCS12_parse_ex() with multiple symmetric keys
+ok(run(test(["pkcs12_api_test",
+             "-in", srctop_file("test", "recipes", "80-test_pkcs12_data", "multi-skey.p12"),
+             "-pass", "password",
+             "-num-skeys", "2",
+             ])), "Test PKCS12_parse_ex() with multiple symmetric keys");
+
+# Test OSSL_STORE with multiple symmetric keys
+{
+    my @output = run(app(["openssl", "storeutl",
+                         "-passin", "pass:password",
+                         srctop_file("test", "recipes", "80-test_pkcs12_data", "multi-skey.p12")]),
+                    capture => 1);
+    ok(@output > 0, "Test OSSL_STORE loads multiple symmetric keys from PKCS#12");
+
+    my $output_text = join("", @output);
+    my @skey_matches = ($output_text =~ /Symmetric key/g);
+    ok(scalar @skey_matches == 2, "OSSL_STORE output shows two symmetric keys");
+}
+
 SetConsoleOutputCP($savedcp) if (defined($savedcp));