Commit 4dcc3b19d1 for openssl.org
commit 4dcc3b19d1f6c81b9a847560869defe33f73b4f7
Author: slontis <shane.lontis@oracle.com>
Date: Wed Sep 13 10:26:47 2023 +1000
Add PKCS12_check_version() API
There was no check for the version = 3 when loading a PKCS #12 file.
Rather than adding multiple _ex() functions that support checking
the version number, a separate API has been added to do the check,
which can be called after one of the d2i_PKCS12 functions.
The check has been added to the openssl pkcs12 command.
It is checked by default and can be disabled by using the option
-noversioncheck.
Resolves: https://github.com/openssl/openssl/issues/22030
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
Merge-date: Fri Oct 30 11:43:02 2026
Merged-from: https://github.com/openssl/openssl/pull/22087
diff --git a/CHANGES.md b/CHANGES.md
index a414f7076d..f6cb395529 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -128,6 +128,13 @@ OpenSSL 4.2
*Dmitry Belyavskiy*
+ * Added `PKCS12_check_version()` API function, that verifies that the loaded
+ PKCS #12 file has its version in accordance with [RFC 7292 Section 4].
+ The check has been enabled in the `openssl pkcs12` command by default,
+ and can be disabled using the newly introduced `-noversioncheck` option.
+
+ *Shane Lontis*
+
* Added the ASCON-AEAD128 cipher as specified in NIST SP 800-232. ASCON-AEAD128
provides authenticated encryption with associated data (AEAD) using 128-bit
keys, nonces, and tags. The cipher is available through the EVP interface and
@@ -24642,6 +24649,7 @@ ndif
[RFC 5297]: https://datatracker.ietf.org/doc/html/rfc5297
[RFC 5652]: https://datatracker.ietf.org/doc/html/rfc5652
[RFC 7250]: https://datatracker.ietf.org/doc/html/rfc7250
+[RFC 7292 Section 4]: https://datatracker.ietf.org/doc/html/rfc7292#section-4
[RFC 7685]: https://datatracker.ietf.org/doc/html/rfc7685
[RFC 7919]: https://datatracker.ietf.org/doc/html/rfc7919
[RFC 8422]: https://datatracker.ietf.org/doc/html/rfc8422
diff --git a/apps/pkcs12.c b/apps/pkcs12.c
index 841eb8f98c..96d04b1868 100644
--- a/apps/pkcs12.c
+++ b/apps/pkcs12.c
@@ -110,6 +110,7 @@ typedef enum OPTION_choice {
OPT_JDKTRUST,
OPT_PBMAC1_PBKDF2,
OPT_PBMAC1_PBKDF2_MD,
+ OPT_NOVERSIONCHECK,
#ifndef OPENSSL_NO_DES
OPT_LEGACY_ALG
#endif
@@ -192,6 +193,8 @@ const OPTIONS pkcs12_options[] = {
{ "macsaltlen", OPT_MACSALTLEN, 'p', "Specify the salt len for MAC" },
{ "nomac", OPT_NOMAC, '-', "Don't generate MAC" },
{ "jdktrust", OPT_JDKTRUST, 's', "Mark certificate in PKCS #12 store as trusted for JDK compatibility" },
+ { "noversioncheck", OPT_NOVERSIONCHECK, '-',
+ "Don't check the loaded PKCS #12 version info is valid" },
{ NULL }
};
@@ -207,6 +210,7 @@ int pkcs12_main(int argc, char **argv)
#ifndef OPENSSL_NO_DES
int use_legacy = 0;
#endif
+ int version_check = 1;
/* use library defaults for the iter, maciter, cert, and key PBE */
int iter = 0, maciter = 0, pbmac1_pbkdf2 = 0;
int macsaltlen = PKCS12_SALT_LEN;
@@ -404,6 +408,9 @@ int pkcs12_main(int argc, char **argv)
use_legacy = 1;
break;
#endif
+ case OPT_NOVERSIONCHECK:
+ version_check = 0;
+ break;
case OPT_PROV_CASES:
if (!opt_provider(o))
goto end;
@@ -788,6 +795,10 @@ int pkcs12_main(int argc, char **argv)
ERR_print_errors(bio_err);
goto end;
}
+ if (version_check && !PKCS12_check_version(p12)) {
+ BIO_printf(bio_err, "PKCS #12 file contains an invalid version number\n");
+ goto end;
+ }
if (!noprompt) {
if (1) {
diff --git a/crypto/pkcs12/p12_init.c b/crypto/pkcs12/p12_init.c
index fe264a89d6..8ad78e6dd1 100644
--- a/crypto/pkcs12/p12_init.c
+++ b/crypto/pkcs12/p12_init.c
@@ -13,8 +13,10 @@
#include "crypto/pkcs7.h"
#include "p12_local.h"
-/* Initialise a PKCS12 structure to take data */
+/* Refer to https://datatracker.ietf.org/doc/html/rfc7292#section-4 */
+#define PKCS12_VERSION 3
+/* Initialise a PKCS12 structure to take data */
PKCS12 *PKCS12_init_ex(int mode, OSSL_LIB_CTX *ctx, const char *propq)
{
PKCS12 *pkcs12;
@@ -23,7 +25,7 @@ PKCS12 *PKCS12_init_ex(int mode, OSSL_LIB_CTX *ctx, const char *propq)
ERR_raise(ERR_LIB_PKCS12, ERR_R_ASN1_LIB);
return NULL;
}
- if (!ASN1_INTEGER_set(pkcs12->version, 3))
+ if (!ASN1_INTEGER_set(pkcs12->version, PKCS12_VERSION))
goto err;
pkcs12->authsafes->type = OBJ_nid2obj(mode);
@@ -51,6 +53,13 @@ err:
return NULL;
}
+int PKCS12_check_version(const PKCS12 *p12)
+{
+ if (p12 == NULL)
+ return 0;
+ return ASN1_INTEGER_get(p12->version) == PKCS12_VERSION;
+}
+
PKCS12 *PKCS12_init(int mode)
{
return PKCS12_init_ex(mode, NULL, NULL);
diff --git a/demos/pkcs12/pkread.c b/demos/pkcs12/pkread.c
index 29bd9b21e1..d26f890370 100644
--- a/demos/pkcs12/pkread.c
+++ b/demos/pkcs12/pkread.c
@@ -13,7 +13,7 @@
#include <openssl/err.h>
#include <openssl/pkcs12.h>
-/* Simple PKCS#12 file reader */
+/* Simple PKCS #12 file reader */
static char *find_friendly_name(PKCS12 *p12)
{
@@ -67,12 +67,17 @@ int main(int argc, char **argv)
p12 = d2i_PKCS12_fp(fp, NULL);
fclose(fp);
if (p12 == NULL) {
- fprintf(stderr, "Error reading PKCS#12 file\n");
+ fprintf(stderr, "Error reading PKCS #12 file\n");
+ ERR_print_errors_fp(stderr);
+ goto err;
+ }
+ if (!PKCS12_check_version(p12)) {
+ fprintf(stderr, "PKCS #12 file has incorrect version\n");
ERR_print_errors_fp(stderr);
goto err;
}
if (!PKCS12_parse(p12, argv[2], &pkey, &cert, &ca)) {
- fprintf(stderr, "Error parsing PKCS#12 file\n");
+ fprintf(stderr, "Error parsing PKCS #12 file\n");
ERR_print_errors_fp(stderr);
goto err;
}
diff --git a/doc/build.info b/doc/build.info
index 2f400822d7..3ee1e0d8ee 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -1963,6 +1963,10 @@ DEPEND[html/man3/PKCS12_add_safe.html]=man3/PKCS12_add_safe.pod
GENERATE[html/man3/PKCS12_add_safe.html]=man3/PKCS12_add_safe.pod
DEPEND[man/man3/PKCS12_add_safe.3]=man3/PKCS12_add_safe.pod
GENERATE[man/man3/PKCS12_add_safe.3]=man3/PKCS12_add_safe.pod
+DEPEND[html/man3/PKCS12_check_version.html]=man3/PKCS12_check_version.pod
+GENERATE[html/man3/PKCS12_check_version.html]=man3/PKCS12_check_version.pod
+DEPEND[man/man3/PKCS12_check_version.3]=man3/PKCS12_check_version.pod
+GENERATE[man/man3/PKCS12_check_version.3]=man3/PKCS12_check_version.pod
DEPEND[html/man3/PKCS12_create.html]=man3/PKCS12_create.pod
GENERATE[html/man3/PKCS12_create.html]=man3/PKCS12_create.pod
DEPEND[man/man3/PKCS12_create.3]=man3/PKCS12_create.pod
@@ -3570,6 +3574,7 @@ html/man3/PKCS12_add_cert.html \
html/man3/PKCS12_add_friendlyname_asc.html \
html/man3/PKCS12_add_localkeyid.html \
html/man3/PKCS12_add_safe.html \
+html/man3/PKCS12_check_version.html \
html/man3/PKCS12_create.html \
html/man3/PKCS12_decrypt_secretbag.html \
html/man3/PKCS12_decrypt_skey.html \
@@ -4252,6 +4257,7 @@ man/man3/PKCS12_add_cert.3 \
man/man3/PKCS12_add_friendlyname_asc.3 \
man/man3/PKCS12_add_localkeyid.3 \
man/man3/PKCS12_add_safe.3 \
+man/man3/PKCS12_check_version.3 \
man/man3/PKCS12_create.3 \
man/man3/PKCS12_decrypt_secretbag.3 \
man/man3/PKCS12_decrypt_skey.3 \
diff --git a/doc/man1/openssl-pkcs12.pod.in b/doc/man1/openssl-pkcs12.pod.in
index 8cd57c3812..7674d0bc59 100644
--- a/doc/man1/openssl-pkcs12.pod.in
+++ b/doc/man1/openssl-pkcs12.pod.in
@@ -44,6 +44,7 @@ PKCS#12 input (parsing) options:
[B<-idea>]
[B<-noenc>]
[B<-nodes>]
+[B<-noversioncheck>]
PKCS#12 output (export) options:
@@ -143,6 +144,11 @@ No certificates will be output.
This option inhibits all credentials output,
and so the input is just verified.
+=item B<-noversioncheck>
+
+This option disables checking of the version information.
+This should not be required, but is supplied for backwards compatibility.
+
=item B<-legacy>
Use legacy mode of operation and automatically load the legacy provider.
@@ -489,10 +495,13 @@ L<ossl_store-file(7)>
=head1 HISTORY
The B<-nodes> option was deprecated in OpenSSL 3.0, too; use B<-noenc> instead.
+
The B<-macsaltlen> option default changed from 8 to 16 bytes in OpenSSL 3.6.
The B<-engine> option was removed in OpenSSL 4.0.
+The B<-noversioncheck> option was added in OpenSSL 4.2.
+
=head1 COPYRIGHT
Copyright 2000-2026 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man3/PKCS12_check_version.pod b/doc/man3/PKCS12_check_version.pod
new file mode 100644
index 0000000000..407a5d72de
--- /dev/null
+++ b/doc/man3/PKCS12_check_version.pod
@@ -0,0 +1,59 @@
+=pod
+
+=head1 NAME
+
+PKCS12_check_version - Checks the version of a PKCS #12 structure
+
+=head1 SYNOPSIS
+
+ #include <openssl/pkcs12.h>
+
+ int PKCS12_check_version(const PKCS12 *p12);
+
+=head1 DESCRIPTION
+
+PKCS12_check_version() checks that the version info of a I<p12> PKCS #12 object
+is correct.
+
+=head1 NOTES
+
+A PKCS12 object can be loaded using d2i_PKC12(), d2i_PKC12_bio()
+or d2i_PKC12_fp(). The loaded object contains a version number, which should
+have the value of 3, in accordance with RFC 7292.
+As this version number was not previously checked on loading,
+adding the check to these functions would potentially break existing
+PKCS12 files. Instead of adding an B<_ex> variant of all these functions,
+the function PKCS12_check_version() has been added instead.
+PKCS12_check_version() should be called after loading a PKCS #12
+object, before calling either PKCS12_parse() or PKCS12_unpack_authsafes().
+
+=head1 RETURN VALUES
+
+PKCS12_check_version() returns 1 if the version info of a I<p12> object
+is correct, otherwise it returns 0.
+
+=head1 CONFORMING TO
+
+IETF RFC 7292 (L<https://tools.ietf.org/html/rfc7292>).
+
+=head1 SEE ALSO
+
+d2i_PKCS12(3),
+d2i_PKC12_bio(3),
+d2i_PKC12_fp(3),
+PKCS12_parse(3).
+
+=head1 HISTORY
+
+PKCS12_check_version() was added in OpenSSL 4.2.
+
+=head1 COPYRIGHT
+
+Copyright 2026 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/include/openssl/pkcs12.h.in b/include/openssl/pkcs12.h.in
index 1c470f15e0..2e056aa26c 100644
--- a/include/openssl/pkcs12.h.in
+++ b/include/openssl/pkcs12.h.in
@@ -322,6 +322,7 @@ int PKCS12_parse_ex(PKCS12 *p12, const char *pass,
int PKCS12_parse(PKCS12 *p12, const char *pass, EVP_PKEY **pkey, X509 **cert,
STACK_OF(X509) **ca);
+int PKCS12_check_version(const PKCS12 *p12);
typedef int PKCS12_create_cb(PKCS12_SAFEBAG *bag, void *cbarg);
PKCS12 *PKCS12_create(const char *pass, const char *name, EVP_PKEY *pkey,
X509 *cert, STACK_OF(X509) *ca, int nid_key, int nid_cert,
diff --git a/test/pkcs12_api_test.c b/test/pkcs12_api_test.c
index 940b7625f0..e9a544b61b 100644
--- a/test/pkcs12_api_test.c
+++ b/test/pkcs12_api_test.c
@@ -60,6 +60,7 @@ static const char *in_pass = "";
static int has_key = 0;
static int has_cert = 0;
static int has_ca = 0;
+static int version_pass = 1;
static int expected_ca_count = -1;
static const char *expected_cert_file = NULL;
static const char *expected_ca_file = NULL;
@@ -122,6 +123,8 @@ static int pkcs12_parse_test(void)
if (!TEST_ptr(p12))
goto err;
+ if (!TEST_int_eq(PKCS12_check_version(p12), version_pass))
+ goto err;
if (!TEST_true(PKCS12_parse(p12, in_pass, &key, &cert, &ca)))
goto err;
@@ -559,6 +562,25 @@ err:
return ret;
}
+static int pkcs12_check_version_test(void)
+{
+ int ret = 0;
+ PKCS12 *p12 = NULL;
+
+ if (in_file != NULL) {
+ p12 = PKCS12_load(in_file);
+ if (!TEST_ptr(p12))
+ goto err;
+
+ if (!TEST_int_eq(PKCS12_check_version(p12), version_pass))
+ goto err;
+ }
+ ret = 1;
+err:
+ PKCS12_free(p12);
+ return ret;
+}
+
typedef enum OPTION_choice {
OPT_ERR = -1,
OPT_EOF = 0,
@@ -574,6 +596,7 @@ typedef enum OPTION_choice {
OPT_MISMATCHED_P12,
OPT_IN_NUM_SKEYS,
OPT_LEGACY,
+ OPT_BAD_VERSION,
OPT_TEST_ENUM
} OPTION_CHOICE;
@@ -593,6 +616,7 @@ const OPTIONS *test_get_options(void)
{ "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" },
+ { "badversion", OPT_BAD_VERSION, '-', "Test is expected to fail the version info check" },
{ NULL }
};
return options;
@@ -844,6 +868,9 @@ int setup_tests(void)
case OPT_IN_NUM_SKEYS:
num_skeys = opt_int_arg();
break;
+ case OPT_BAD_VERSION:
+ version_pass = 0;
+ break;
case OPT_TEST_CASES:
break;
default:
@@ -857,19 +884,22 @@ int setup_tests(void)
return 0;
}
- ADD_TEST(test_null_args);
- ADD_TEST(pkcs12_parse_test);
- ADD_ALL_TESTS(test_parse_combinations, 8);
- ADD_ALL_TESTS(test_parse_cert_placement, 8);
- ADD_TEST(test_parse_mismatched_key_password);
- ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_mfail_test);
- ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_existing_ca_mfail_test);
- 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);
+ ADD_TEST(pkcs12_check_version_test);
+ if (version_pass) {
+ ADD_TEST(test_null_args);
+ ADD_TEST(pkcs12_parse_test);
+ ADD_ALL_TESTS(test_parse_combinations, 8);
+ ADD_ALL_TESTS(test_parse_cert_placement, 8);
+ ADD_TEST(test_parse_mismatched_key_password);
+ ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_mfail_test);
+ ADD_MFAIL_NO_CHECK_TEST(pkcs12_parse_existing_ca_mfail_test);
+ 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 deea098c8b..557b886e10 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 => 119 + ($no_fips ? 0 : 5);
+plan tests => 122 + ($no_fips ? 0 : 5);
# Test different PKCS#12 formats
ok(run(test(["pkcs12_format_test"])), "test pkcs12 formats");
@@ -892,4 +892,23 @@ ok(run(test(["pkcs12_api_test",
ok(scalar @skey_matches == 2, "OSSL_STORE output shows two symmetric keys");
}
+my $badversionfile = srctop_file("test", "recipes", "80-test_pkcs12_data",
+ "bad_version.p12");
+
+ok(run(app(["openssl", "pkcs12", "-info", "-noout",
+ "-in", $badversionfile,
+ "-noversioncheck",
+ "-passin", "pass:password"])),
+ "Test that a PKCS #12 file with a bad version and -noversioncheck passes");
+ok(!run(app(["openssl", "pkcs12", "-info", "-noout",
+ "-in", $badversionfile,
+ "-passin", "pass:password"])),
+ "Test that a PKCS #12 file with a bad version fails");
+
+ok(run(test(["pkcs12_api_test",
+ "-in", $badversionfile,
+ "-pass", "password",
+ "-badversion"])),
+ "Test PKCS #12 version info is invalid");
+
SetConsoleOutputCP($savedcp) if (defined($savedcp));
diff --git a/test/recipes/80-test_pkcs12_data/bad_version.p12 b/test/recipes/80-test_pkcs12_data/bad_version.p12
new file mode 100644
index 0000000000..96dddf87f9
Binary files /dev/null and b/test/recipes/80-test_pkcs12_data/bad_version.p12 differ
diff --git a/util/libcrypto.num b/util/libcrypto.num
index ab4d1e440b..c9809d8ef2 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5747,3 +5747,4 @@ 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:
+PKCS12_check_version ? 4_2_0 EXIST::FUNCTION: