Commit b68e796170 for openssl.org

commit b68e7961700a06ddadcec386ded6ff3816af3b24
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date:   Tue Apr 21 19:23:17 2026 +0200

    Add tests for Java PKCS#12 symmetric key support

    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:24 2026
    (Merged from https://github.com/openssl/openssl/pull/30937)

diff --git a/test/pkcs12_api_test.c b/test/pkcs12_api_test.c
index 5f4550fe3b..940b7625f0 100644
--- a/test/pkcs12_api_test.c
+++ b/test/pkcs12_api_test.c
@@ -65,6 +65,7 @@ static const char *expected_cert_file = NULL;
 static const char *expected_ca_file = NULL;
 static const char *expected_key_file = NULL;
 static int mismatched_key_pass = 0;
+static int num_skeys = 0;

 static int changepass(PKCS12 *p12, EVP_PKEY *key, X509 *cert, STACK_OF(X509) *ca)
 {
@@ -383,6 +384,181 @@ err:
     return TEST_true(ret);
 }

+static int test_parse_ex_libctx(int idx)
+{
+    int ret = 0;
+    BIO *bio = NULL;
+    PKCS12 *p12 = NULL;
+    PKCS12_PARSE_CTX *ctx = NULL;
+    EVP_PKEY *key = NULL;
+    X509 *cert = NULL;
+    int prebound = (idx == 0);
+
+    if (in_file == NULL || !has_key || !has_cert || mismatched_key_pass)
+        return 1;
+
+    TEST_info("libctx propagation: %s decode", prebound ? "prebound" : "ordinary");
+
+    bio = BIO_new_file(in_file, "rb");
+    if (!TEST_ptr(bio))
+        goto err;
+
+    if (prebound) {
+        p12 = PKCS12_init_ex(NID_pkcs7_data, testctx, "provider=default");
+        if (!TEST_ptr(p12))
+            goto err;
+    }
+    if (!TEST_ptr(d2i_PKCS12_bio(bio, &p12)))
+        goto err;
+    BIO_free(bio);
+    bio = NULL;
+
+    if (!TEST_ptr(ctx = PKCS12_PARSE_CTX_new()))
+        goto err;
+    PKCS12_PARSE_CTX_set_pkey(ctx, &key);
+    PKCS12_PARSE_CTX_set_cert(ctx, &cert);
+
+    if (!TEST_true(PKCS12_parse_ex(p12, in_pass, ctx,
+            testctx, "provider=default")))
+        goto err;
+
+    if (!TEST_ptr(key) || !TEST_ptr(cert))
+        goto err;
+
+    ret = 1;
+
+err:
+    BIO_free(bio);
+    PKCS12_PARSE_CTX_free(ctx);
+    PKCS12_free(p12);
+    EVP_PKEY_free(key);
+    X509_free(cert);
+    return ret;
+}
+
+static int test_parse_ex_skey(void)
+{
+    PKCS12 *p12 = NULL;
+    PKCS12_PARSE_CTX *ctx = NULL;
+    EVP_PKEY *pkey = NULL;
+    X509 *cert = NULL;
+    STACK_OF(X509) *ca = NULL;
+    STACK_OF(EVP_SKEY) *skeys = NULL;
+    EVP_SKEY *skey = NULL;
+    const unsigned char *raw_key = NULL;
+    size_t raw_key_len = 0;
+    int ret = 0;
+
+    if (in_file == NULL || mismatched_key_pass)
+        return 1;
+
+    if (!TEST_ptr(p12 = PKCS12_load(in_file)))
+        goto err;
+
+    if (!TEST_ptr(ctx = PKCS12_PARSE_CTX_new()))
+        goto err;
+
+    PKCS12_PARSE_CTX_set_pkey(ctx, &pkey);
+    PKCS12_PARSE_CTX_set_cert(ctx, &cert);
+    PKCS12_PARSE_CTX_set_ca(ctx, &ca);
+    PKCS12_PARSE_CTX_set_skeys(ctx, &skeys);
+
+    if (!TEST_true(PKCS12_parse_ex(p12, in_pass, ctx,
+            testctx, "provider=default")))
+        goto err;
+
+    if ((has_key && !TEST_ptr(pkey)) || (!has_key && !TEST_ptr_null(pkey)))
+        goto err;
+    if ((has_cert && !TEST_ptr(cert)) || (!has_cert && !TEST_ptr_null(cert)))
+        goto err;
+    if (num_skeys > 0) {
+        if (!TEST_ptr(skeys)
+            || !TEST_int_eq(sk_EVP_SKEY_num(skeys), num_skeys))
+            goto err;
+    } else {
+        if (!TEST_ptr_null(skeys))
+            goto err;
+    }
+
+    if (num_skeys == 1 && skeys != NULL) {
+        skey = sk_EVP_SKEY_value(skeys, 0);
+        if (!TEST_ptr(skey))
+            goto err;
+        if (!TEST_true(EVP_SKEY_get0_raw_key(skey, &raw_key, &raw_key_len)))
+            goto err;
+        if (!TEST_size_t_eq(raw_key_len, 32))
+            goto err;
+        for (size_t i = 0; i < raw_key_len; i++) {
+            if (!TEST_uchar_eq(raw_key[i], 0x41))
+                goto err;
+        }
+        if (!TEST_str_eq(EVP_SKEY_get0_skeymgmt_name(skey), "AES"))
+            goto err;
+    }
+
+    ret = 1;
+
+err:
+    PKCS12_PARSE_CTX_free(ctx);
+    PKCS12_free(p12);
+    EVP_PKEY_free(pkey);
+    X509_free(cert);
+    OSSL_STACK_OF_X509_free(ca);
+    sk_EVP_SKEY_pop_free(skeys, EVP_SKEY_free);
+    return ret;
+}
+
+static int test_parse_ex_skey_libctx(int idx)
+{
+    PKCS12 *src = NULL, *p12 = NULL;
+    STACK_OF(PKCS7) *safes = NULL;
+    PKCS12_PARSE_CTX *ctx = NULL;
+    STACK_OF(EVP_SKEY) *skeys = NULL;
+    int ret = 0;
+
+    if (in_file == NULL || num_skeys == 0)
+        return 1;
+
+    if (!TEST_ptr(src = PKCS12_load(in_file))
+        || !TEST_ptr(safes = PKCS12_unpack_authsafes(src)))
+        goto err;
+
+    if (!TEST_ptr(p12 = PKCS12_init_ex(NID_pkcs7_data, testctx,
+                      idx < 2 ? "provider=missing" : "provider=default")))
+        goto err;
+
+    /* Omit the MAC so the test reaches decryption of the secret bag. */
+    if (!TEST_true(PKCS12_pack_authsafes(p12, safes))
+        || !TEST_ptr(ctx = PKCS12_PARSE_CTX_new()))
+        goto err;
+    PKCS12_PARSE_CTX_set_skeys(ctx, &skeys);
+
+    if (idx < 2) {
+        /* Neither NULL nor an empty query should use the stored query. */
+        if (!TEST_true(PKCS12_parse_ex(p12, in_pass, ctx,
+                testctx, idx == 0 ? NULL : ""))
+            || !TEST_ptr(skeys)
+            || !TEST_int_eq(sk_EVP_SKEY_num(skeys), num_skeys))
+            goto err;
+    } else {
+        /* The default library context contains only the null provider. */
+        if (!TEST_false(PKCS12_parse_ex(p12, in_pass, ctx,
+                NULL, idx == 2 ? NULL : "provider=default"))
+            || !TEST_ptr_null(skeys))
+            goto err;
+        ERR_clear_error();
+    }
+
+    ret = 1;
+err:
+    sk_EVP_SKEY_pop_free(skeys, EVP_SKEY_free);
+    PKCS12_PARSE_CTX_free(ctx);
+    sk_PKCS7_pop_free(safes, PKCS7_free);
+    PKCS12_free(p12);
+    PKCS12_free(src);
+    return ret;
+}
+
 typedef enum OPTION_choice {
     OPT_ERR = -1,
     OPT_EOF = 0,
@@ -396,6 +572,7 @@ typedef enum OPTION_choice {
     OPT_EXPECTED_CA,
     OPT_EXPECTED_KEY,
     OPT_MISMATCHED_P12,
+    OPT_IN_NUM_SKEYS,
     OPT_LEGACY,
     OPT_TEST_ENUM
 } OPTION_CHOICE;
@@ -414,6 +591,7 @@ const OPTIONS *test_get_options(void)
         { "expected-ca", OPT_EXPECTED_CA, '<', "PEM file of expected CA certificates in order" },
         { "expected-key", OPT_EXPECTED_KEY, '<', "PEM file of expected private key" },
         { "mismatched-key-pass", OPT_MISMATCHED_P12, '-', "Input has key encrypted with a different password" },
+        { "num-skeys", OPT_IN_NUM_SKEYS, 'n', "Number of symmetric keys in the input file" },
         { "legacy", OPT_LEGACY, '-', "Test the legacy APIs" },
         { NULL }
     };
@@ -430,11 +608,18 @@ static int test_PKCS12_set_pbmac1_pbkdf2_saltlen_zero(void)

     if (mismatched_key_pass)
         return TEST_skip("not applicable with mismatched key password");
+
     if (!TEST_ptr(p12 = PKCS12_load(in_file)))
         return 0;
     if (!TEST_true(PKCS12_parse(p12, in_pass, &key, &cert, &ca)))
         goto err;
     PKCS12_free(p12);
+    p12 = NULL;
+
+    if (key == NULL && cert == NULL && ca == NULL) {
+        ret = 1;
+        goto err;
+    }

     if (!TEST_ptr(p12 = PKCS12_create_ex2("pass", NULL, key, cert, ca,
                       NID_undef, NID_undef, 0, -1, 0,
@@ -460,11 +645,18 @@ static int test_PKCS12_set_pbmac1_pbkdf2_invalid_saltlen(void)

     if (mismatched_key_pass)
         return TEST_skip("not applicable with mismatched key password");
+
     if (!TEST_ptr(p12 = PKCS12_load(in_file)))
         return 0;
     if (!TEST_true(PKCS12_parse(p12, in_pass, &key, &cert, &ca)))
         goto err;
     PKCS12_free(p12);
+    p12 = NULL;
+
+    if (key == NULL && cert == NULL && ca == NULL) {
+        ret = 1;
+        goto err;
+    }

     if (!TEST_ptr(p12 = PKCS12_create_ex2("pass", NULL, key, cert, ca,
                       NID_undef, NID_undef, 0, -1, 0,
@@ -649,6 +841,9 @@ int setup_tests(void)
         case OPT_MISMATCHED_P12:
             mismatched_key_pass = 1;
             break;
+        case OPT_IN_NUM_SKEYS:
+            num_skeys = opt_int_arg();
+            break;
         case OPT_TEST_CASES:
             break;
         default:
@@ -672,6 +867,9 @@ int setup_tests(void)
     ADD_ALL_TESTS(pkcs12_create_ex2_test, 3);
     ADD_TEST(test_PKCS12_set_pbmac1_pbkdf2_saltlen_zero);
     ADD_TEST(test_PKCS12_set_pbmac1_pbkdf2_invalid_saltlen);
+    ADD_TEST(test_parse_ex_skey);
+    ADD_ALL_TESTS(test_parse_ex_libctx, 2);
+    ADD_ALL_TESTS(test_parse_ex_skey_libctx, 4);
     return 1;
 }

diff --git a/test/recipes/80-test_pkcs12.t b/test/recipes/80-test_pkcs12.t
index 862a81abbc..55afbd259c 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 => 68 + ($no_fips ? 0 : 5);
+plan tests => 70 + ($no_fips ? 0 : 5);

 # Test different PKCS#12 formats
 ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats");
@@ -367,6 +367,16 @@ subtest "pkcs12 -clcerts/-cacerts filtering and -name" => sub {
        "-cacerts outputs the CA certificate");
 };

+# Test PKCS12_parse_ex libctx propagation (PR #30937)
+# mixed.p12 uses AES-encrypted cert safe, exercising PKCS7 context propagation
+ok(run(test(["pkcs12_api_test",
+             "-in", "mixed.p12",
+             "-pass", "",
+             "-has-key", 1,
+             "-has-cert", 1,
+             "-has-ca", 1,
+             ])), "Test PKCS12_parse_ex libctx propagation (PR #30937)");
+
 # Tests for pkcs12_parse
 ok(run(test(["pkcs12_api_test",
              "-in", $outfile1,
@@ -603,4 +613,12 @@ unless ($no_fips) {
     }
 }

+
+# Test PKCS12_parse_ex() with Java symmetric key file
+ok(run(test(["pkcs12_api_test",
+             "-in", srctop_file("test", "recipes", "80-test_pkcs12_data", "java-skey.p12"),
+             "-pass", "password",
+             "-num-skeys", "1",
+             ])), "Test PKCS12_parse_ex() with symmetric key");
+
 SetConsoleOutputCP($savedcp) if (defined($savedcp));
diff --git a/test/recipes/80-test_pkcs12_data/java-skey.p12 b/test/recipes/80-test_pkcs12_data/java-skey.p12
new file mode 100644
index 0000000000..46c347bef6
Binary files /dev/null and b/test/recipes/80-test_pkcs12_data/java-skey.p12 differ
diff --git a/test/recipes/80-test_pkcs12_data/multi-skey.p12 b/test/recipes/80-test_pkcs12_data/multi-skey.p12
new file mode 100644
index 0000000000..8db996c1d1
Binary files /dev/null and b/test/recipes/80-test_pkcs12_data/multi-skey.p12 differ