Commit 95f35c26f3 for openssl.org

commit 95f35c26f3482e9f05391232d1645fca73da0be5
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date:   Thu Sep 24 15:59:26 2026 +0200

    Dealing with symmetric keys in pkcs12 command-line utility

    This is a follow-up to #30937
    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: Tue Sep 29 18:34:13 2026
    (Merged from https://github.com/openssl/openssl/pull/32978)

diff --git a/apps/pkcs12.c b/apps/pkcs12.c
index 2deb87a1f8..f97149f8f7 100644
--- a/apps/pkcs12.c
+++ b/apps/pkcs12.c
@@ -1036,6 +1036,51 @@ int dump_certs_pkeys_bags(BIO *out, const STACK_OF(PKCS12_SAFEBAG) *bags,
     return 1;
 }

+static int extract_symmetric_key(BIO *out, const PKCS12_SAFEBAG *bag,
+    const char *pass, int passlen, int options, const EVP_CIPHER *enc)
+{
+    PKCS8_PRIV_KEY_INFO *p8;
+    EVP_SKEY *skey = NULL;
+
+    if (options & NOKEYS)
+        return 1;
+
+    p8 = PKCS12_decrypt_secretbag(bag, pass, passlen,
+        app_get0_libctx(), app_get0_propq());
+    if (p8 == NULL)
+        return 0;
+    skey = PKCS8_PRIV_KEY_INFO_get1_skey(p8, app_get0_libctx(),
+        app_get0_propq(), NULL, 0);
+    PKCS8_PRIV_KEY_INFO_free(p8);
+    if (skey == NULL)
+        return 0;
+    if (enc == NULL) {
+        const unsigned char *kdata = NULL;
+        size_t klen = 0;
+
+        EVP_SKEY_get0_raw_key(skey, &kdata, &klen);
+
+        if (kdata != NULL && klen > 0) {
+            BIO_puts(bio_err, "Bag Value: ");
+            BIO_puts(out, "\n    Key Data: ");
+            hex_print(out, kdata, (int)klen);
+
+            BIO_printf(bio_err, "    Key management: %s\n", EVP_SKEY_get0_skeymgmt_name(skey));
+            BIO_printf(bio_err, "    Key Length: %zu bytes\n", klen);
+        } else {
+            BIO_printf(bio_err, "Error obtaining key bytes\n");
+            EVP_SKEY_free(skey);
+            return 0;
+        }
+    } else {
+        BIO_puts(bio_err, "Bag Value: ");
+        print_attribute(out, PKCS12_SAFEBAG_get0_bag_obj(bag));
+    }
+
+    EVP_SKEY_free(skey);
+    return 1;
+}
+
 int dump_certs_pkeys_bag(BIO *out, const PKCS12_SAFEBAG *bag,
     const char *pass, int passlen, int options,
     char *pempass, const EVP_CIPHER *enc)
@@ -1115,8 +1160,12 @@ int dump_certs_pkeys_bag(BIO *out, const PKCS12_SAFEBAG *bag,
         print_attribs(out, attrs, "Bag Attributes");
         BIO_puts(bio_err, "Bag Type: ");
         i2a_ASN1_OBJECT(bio_err, PKCS12_SAFEBAG_get0_bag_type(bag));
-        BIO_puts(bio_err, "\nBag Value: ");
-        print_attribute(out, PKCS12_SAFEBAG_get0_bag_obj(bag));
+        BIO_puts(bio_err, "\n");
+
+        if (PKCS12_SAFEBAG_get_bag_nid(bag) == NID_pkcs8ShroudedKeyBag)
+            return extract_symmetric_key(out, bag, pass, passlen, options, enc);
+        else
+            print_attribute(out, PKCS12_SAFEBAG_get0_bag_obj(bag));
         return 1;

     case NID_safeContentsBag:
diff --git a/test/recipes/80-test_pkcs12.t b/test/recipes/80-test_pkcs12.t
index a9015c69ee..16652b1fb1 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 => 77 + ($no_fips ? 0 : 5);
+plan tests => 117 + ($no_fips ? 0 : 5);

 # Test different PKCS#12 formats
 ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats");
@@ -630,6 +630,116 @@ unless ($no_fips) {
     }
 }

+# Test Java PKCS#12 files with symmetric keys
+{
+    my $java_p12 = srctop_file("test", "recipes", "80-test_pkcs12_data", "java-skey.p12");
+    my $java_err = "java-skey.err";
+    my @err_lines;
+    my $fh;
+    my $key_pattern = "41 41 41 41 41 41";
+
+    # Test 1: Info display shows bag attributes
+    my @java_pkcs12info = run(app(["openssl", "pkcs12", "-info", "-in", $java_p12,
+                                   "-passin", "pass:password"]), capture => 1);
+
+    ok(grep(/friendlyName:\s+my-explicit-key/, @java_pkcs12info) == 1,
+       "test Java PKCS#12 friendly name in output");
+
+    ok(grep(/localKeyID:/, @java_pkcs12info) == 1,
+       "test Java PKCS#12 localKeyID in output");
+
+    # Test 2: Default extraction (without -noenc) should NOT output key data
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-in", $java_p12, "-nocerts",
+                "-passin", "pass:password"], stderr => $java_err), capture => 1);
+    ok(scalar @java_pkcs12info > 0,
+       "test Java PKCS#12 extract without -noenc");
+
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok(grep(/Bag Attributes/, @java_pkcs12info) == 1,
+       "test extracted output contains bag attributes");
+    ok(grep(/Key management/, @err_lines) == 0 && grep(/Key Length:/, @err_lines) == 0,
+       "test extracted output does not contain algorithm and key length without -noenc");
+    ok(grep(/$key_pattern/, @java_pkcs12info, @err_lines) == 0,
+       "test extracted output does NOT contain hex key bytes");
+
+    # Test 3: Extract with -noenc should output key data in hex
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-in", $java_p12, "-nocerts",
+                "-passin", "pass:password", "-noenc"], stderr => $java_err), capture => 1);
+    ok(scalar @java_pkcs12info > 0,
+       "test Java PKCS#12 extract with -noenc");
+
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok(grep(/Bag Attributes/, @java_pkcs12info) == 1,
+       "test extracted key contains bag attributes");
+    ok(grep(/Key management/, @err_lines) == 1
+        && grep(/Key Length: 32 bytes/, @err_lines) == 1 && grep(/Key Data:/, @java_pkcs12info) == 1,
+       "test extracted key contains algorithm, key length and key data labels");
+    ok(grep(/$key_pattern/, @java_pkcs12info) == 1,
+       "test extracted key contains hex data");
+
+    # Test 4: -info -noout does not write to the output BIO
+    ok(run(app(["openssl", "pkcs12", "-info", "-noout", "-in", $java_p12,
+                "-passin", "pass:password"])),
+       "test Java PKCS#12 -info -noout succeeds");
+
+    # Test 5: -noout should not output key material
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-noout", "-in", $java_p12,
+                "-passin", "pass:password"], stderr => $java_err),
+                capture => 1, statusvar => \my $noout_exit);
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($noout_exit, "test Java PKCS#12 -noout succeeds");
+    ok(grep(/$key_pattern/, @java_pkcs12info, @err_lines) == 0,
+       "test Java PKCS#12 -noout does not output key hex data");
+    ok(grep(/Key Data:/, @java_pkcs12info) == 0,
+       "test Java PKCS#12 -noout does not output Key Data label");
+
+    # Test 6: -noout -noenc should still suppress key material
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-noout", "-noenc", "-in", $java_p12,
+                "-passin", "pass:password"], stderr => $java_err),
+                capture => 1, statusvar => \my $noout_noenc_exit);
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($noout_noenc_exit, "test Java PKCS#12 -noout -noenc succeeds");
+    ok(grep(/$key_pattern/, @java_pkcs12info, @err_lines) == 0,
+       "test Java PKCS#12 -noout -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @java_pkcs12info) == 0,
+       "test Java PKCS#12 -noout -noenc does not output Key Data label");
+
+    # Test 7: -nokeys -noenc should suppress key material
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-nokeys", "-noenc", "-in", $java_p12,
+                "-passin", "pass:password"], stderr => $java_err),
+                capture => 1, statusvar => \my $nokeys_noenc_exit);
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($nokeys_noenc_exit, "test Java PKCS#12 -nokeys -noenc succeeds");
+    ok(grep(/$key_pattern/, @java_pkcs12info, @err_lines) == 0,
+       "test Java PKCS#12 -nokeys -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @java_pkcs12info) == 0,
+       "test Java PKCS#12 -nokeys -noenc does not output Key Data label");
+
+    # Test 8: -info -noout -noenc should suppress key material
+    @java_pkcs12info = run(app(["openssl", "pkcs12", "-info", "-noout", "-noenc", "-in", $java_p12,
+                "-passin", "pass:password"], stderr => $java_err),
+                capture => 1, statusvar => \my $info_noout_noenc_exit);
+    open($fh, '<', $java_err) or die "Can't open file $java_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($info_noout_noenc_exit, "test Java PKCS#12 -info -noout -noenc succeeds");
+    ok(grep(/$key_pattern/, @java_pkcs12info, @err_lines) == 0,
+       "test Java PKCS#12 -info -noout -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @java_pkcs12info) == 0,
+       "test Java PKCS#12 -info -noout -noenc does not output Key Data label");
+
+    unlink $java_err;
+}

 # Test PKCS12_parse_ex() with Java symmetric key file
 ok(run(test(["pkcs12_api_test",
@@ -650,6 +760,102 @@ ok(run(test(["pkcs12_api_test",
     like($output_text, qr/Symmetric key/, "OSSL_STORE output shows symmetric key");
 }

+# Test PKCS#12 files with multiple symmetric keys
+{
+    my $multi_p12 = srctop_file("test", "recipes", "80-test_pkcs12_data", "multi-skey.p12");
+    my $mskey_err = "mskey_stderr.txt";
+    my @err_lines;
+    my $fh;
+    my $key1 = "01 01 01 01 01 01 01 01";
+    my $key2 = "02 02 02 02 02 02 02 02";
+
+    # Test 1: Info display shows both keys with their friendly names
+    my @multi_info = run(app(["openssl", "pkcs12", "-info", "-in", $multi_p12,
+                              "-passin", "pass:password"]), capture => 1);
+
+    ok(grep(/friendlyName:\s+key1/, @multi_info) == 1,
+       "test multi-skey PKCS#12 shows key1 friendly name");
+
+    ok(grep(/friendlyName:\s+key2/, @multi_info) == 1,
+       "test multi-skey PKCS#12 shows key2 friendly name");
+
+    # Test 2: Default extraction shows both keys with metadata
+    @multi_info = run(app(["openssl", "pkcs12", "-in", $multi_p12, "-nocerts",
+                "-passin", "pass:password"],
+                stderr => $mskey_err), capture => 1);
+
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok(grep(/Key Length:/, @multi_info, @err_lines) == 0,
+       "test multi-skey output does not show key lengths without -noenc");
+    ok(grep(/$key1/, @multi_info, @err_lines) == 0 && grep(/$key2/, @multi_info, @err_lines) == 0,
+       "test multi-skey output without -noenc doesn't contain raw key hex data");
+
+    # Test 3: Extract with -noenc should output both keys in hex
+    @multi_info = run(app(["openssl", "pkcs12", "-in", $multi_p12, "-nocerts",
+                "-passin", "pass:password", "-noenc"], stderr => $mskey_err), capture => 1),
+
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok(grep(/$key1/, @multi_info, @err_lines) == 1 && grep(/$key2/, @multi_info, @err_lines) == 1,
+       "test multi-skey output with -noenc contains raw key hex data");
+
+    # Test 4: -noout should not output any key material
+    @multi_info = run(app(["openssl", "pkcs12", "-noout", "-in", $multi_p12,
+                "-passin", "pass:password"], stderr => $mskey_err),
+                capture => 1, statusvar => \my $m_noout_exit);
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($m_noout_exit, "test multi-skey -noout succeeds");
+    ok(grep(/$key1/, @multi_info, @err_lines) == 0 && grep(/$key2/, @multi_info, @err_lines) == 0,
+       "test multi-skey -noout does not output key hex data");
+    ok(grep(/Key Data:/, @multi_info) == 0,
+       "test multi-skey -noout does not output Key Data label");
+
+    # Test 5: -noout -noenc should still suppress key material
+    @multi_info = run(app(["openssl", "pkcs12", "-noout", "-noenc", "-in", $multi_p12,
+                "-passin", "pass:password"], stderr => $mskey_err),
+                capture => 1, statusvar => \my $m_noout_noenc_exit);
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($m_noout_noenc_exit, "test multi-skey -noout -noenc succeeds");
+    ok(grep(/$key1/, @multi_info, @err_lines) == 0 && grep(/$key2/, @multi_info, @err_lines) == 0,
+       "test multi-skey -noout -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @multi_info) == 0,
+       "test multi-skey -noout -noenc does not output Key Data label");
+
+    # Test 6: -nokeys -noenc should suppress key material
+    @multi_info = run(app(["openssl", "pkcs12", "-nokeys", "-noenc", "-in", $multi_p12,
+                "-passin", "pass:password"], stderr => $mskey_err),
+                capture => 1, statusvar => \my $m_nokeys_noenc_exit);
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($m_nokeys_noenc_exit, "test multi-skey -nokeys -noenc succeeds");
+    ok(grep(/$key1/, @multi_info, @err_lines) == 0 && grep(/$key2/, @multi_info, @err_lines) == 0,
+       "test multi-skey -nokeys -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @multi_info) == 0,
+       "test multi-skey -nokeys -noenc does not output Key Data label");
+
+    # Test 7: -info -noout -noenc should suppress key material
+    @multi_info = run(app(["openssl", "pkcs12", "-info", "-noout", "-noenc", "-in", $multi_p12,
+                "-passin", "pass:password"], stderr => $mskey_err),
+                capture => 1, statusvar => \my $m_info_noout_noenc_exit);
+    open $fh, '<', $mskey_err or die "Cannot open $mskey_err: $!";
+    @err_lines = <$fh>;
+    close $fh;
+    ok($m_info_noout_noenc_exit, "test multi-skey -info -noout -noenc succeeds");
+    ok(grep(/$key1/, @multi_info, @err_lines) == 0 && grep(/$key2/, @multi_info, @err_lines) == 0,
+       "test multi-skey -info -noout -noenc does not output key hex data");
+    ok(grep(/Key Data:/, @multi_info) == 0,
+       "test multi-skey -info -noout -noenc does not output Key Data label");
+
+    unlink $mskey_err;
+}

 # Test PKCS12_parse_ex() with multiple symmetric keys
 ok(run(test(["pkcs12_api_test",