Commit 49a9ba5f76 for openssl.org
commit 49a9ba5f76084bb7740452fe4ed835ddeb54cbc2
Author: Dmitry Belyavskiy <beldmit@gmail.com>
Date: Tue Apr 21 19:35:31 2026 +0200
Add documentation for 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:26 2026
(Merged from https://github.com/openssl/openssl/pull/30937)
diff --git a/CHANGES.md b/CHANGES.md
index 5cfdf30c9b..2ce0603f9d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -53,6 +53,15 @@ OpenSSL 4.2
*Madan Mohan Manokar*
+ * Added support for parsing Java-generated PKCS#12 files containing symmetric
+ keys. The `openssl pkcs12` command can now extract symmetric secret keys
+ from PKCS#12 files created by Java's keytool utility. New API functions
+ `PKCS12_parse_ex()` with `PKCS12_PARSE_CTX` for selective extraction of
+ keys, certificates, and symmetric keys from PKCS#12 files.
+ <!-- https://github.com/openssl/openssl/pull/30973 -->
+
+ *Dmitry Belyavskiy*
+
OpenSSL 4.1
-----------
diff --git a/NEWS.md b/NEWS.md
index a58063c68d..de0561eeb9 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -29,7 +29,8 @@ OpenSSL 4.2
### Major changes between OpenSSL 4.1 and OpenSSL 4.2 [under development]
- * none
+ * Added support for Java keytool PKCS#12 files with symmetric keys.
+ New API `PKCS12_parse_ex()` with `PKCS12_PARSE_CTX` has been added.
OpenSSL 4.1
-----------
diff --git a/doc/build.info b/doc/build.info
index 36b742de13..e89862b72b 100644
--- a/doc/build.info
+++ b/doc/build.info
@@ -1963,6 +1963,10 @@ 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
GENERATE[man/man3/PKCS12_create.3]=man3/PKCS12_create.pod
+DEPEND[html/man3/PKCS12_decrypt_secretbag.html]=man3/PKCS12_decrypt_secretbag.pod
+GENERATE[html/man3/PKCS12_decrypt_secretbag.html]=man3/PKCS12_decrypt_secretbag.pod
+DEPEND[man/man3/PKCS12_decrypt_secretbag.3]=man3/PKCS12_decrypt_secretbag.pod
+GENERATE[man/man3/PKCS12_decrypt_secretbag.3]=man3/PKCS12_decrypt_secretbag.pod
DEPEND[html/man3/PKCS12_decrypt_skey.html]=man3/PKCS12_decrypt_skey.pod
GENERATE[html/man3/PKCS12_decrypt_skey.html]=man3/PKCS12_decrypt_skey.pod
DEPEND[man/man3/PKCS12_decrypt_skey.3]=man3/PKCS12_decrypt_skey.pod
@@ -3562,6 +3566,7 @@ html/man3/PKCS12_add_friendlyname_asc.html \
html/man3/PKCS12_add_localkeyid.html \
html/man3/PKCS12_add_safe.html \
html/man3/PKCS12_create.html \
+html/man3/PKCS12_decrypt_secretbag.html \
html/man3/PKCS12_decrypt_skey.html \
html/man3/PKCS12_gen_mac.html \
html/man3/PKCS12_get_friendlyname.html \
@@ -4242,6 +4247,7 @@ man/man3/PKCS12_add_friendlyname_asc.3 \
man/man3/PKCS12_add_localkeyid.3 \
man/man3/PKCS12_add_safe.3 \
man/man3/PKCS12_create.3 \
+man/man3/PKCS12_decrypt_secretbag.3 \
man/man3/PKCS12_decrypt_skey.3 \
man/man3/PKCS12_gen_mac.3 \
man/man3/PKCS12_get_friendlyname.3 \
diff --git a/doc/man3/PKCS12_SAFEBAG_get1_cert.pod b/doc/man3/PKCS12_SAFEBAG_get1_cert.pod
index 28bed08e3d..97ea4a51ae 100644
--- a/doc/man3/PKCS12_SAFEBAG_get1_cert.pod
+++ b/doc/man3/PKCS12_SAFEBAG_get1_cert.pod
@@ -8,7 +8,8 @@ PKCS12_SAFEBAG_get0_bag_obj, PKCS12_SAFEBAG_get0_bag_type,
PKCS12_SAFEBAG_get1_cert_ex, PKCS12_SAFEBAG_get1_cert,
PKCS12_SAFEBAG_get1_crl_ex, PKCS12_SAFEBAG_get1_crl,
PKCS12_SAFEBAG_get0_safes, PKCS12_SAFEBAG_get0_p8inf,
-PKCS12_SAFEBAG_get0_pkcs8 - Get objects from a PKCS#12 safeBag
+PKCS12_SAFEBAG_get0_pkcs8,
+PKCS8_PRIV_KEY_INFO_get1_skey - Get objects from a PKCS#12 safeBag
=head1 SYNOPSIS
@@ -31,6 +32,9 @@ PKCS12_SAFEBAG_get0_pkcs8 - Get objects from a PKCS#12 safeBag
const PKCS8_PRIV_KEY_INFO *PKCS12_SAFEBAG_get0_p8inf(const PKCS12_SAFEBAG *bag);
const X509_SIG *PKCS12_SAFEBAG_get0_pkcs8(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);
+
=head1 DESCRIPTION
PKCS12_SAFEBAG_get0_attr() gets the attribute value corresponding to the B<attr_nid>.
@@ -60,6 +64,14 @@ from a PKCS8shroudedKeyBag or a keyBag.
PKCS12_SAFEBAG_get0_safes() retrieves the set of B<safeBags> contained within a
safeContentsBag.
+PKCS8_PRIV_KEY_INFO_get1_skey() converts a decrypted B<PKCS8_PRIV_KEY_INFO> (as
+returned by L<PKCS12_decrypt_secretbag(3)>) into an B<EVP_SKEY> symmetric key
+object if it holds a symmetric key inside. The algorithm OID in I<p8inf> is
+used to determine the key type (e.g., AES). If the algorithm OID is not
+recognized by current implementation as matching the known key type, a generic
+symmetric key type will be used. The caller can use L<EVP_SKEY_get0_algorithm(3)>
+to retrieve the original AlgorithmIdentifier.
+
=head1 RETURN VALUES
PKCS12_SAFEBAG_get_nid() and PKCS12_SAFEBAG_get_bag_nid() return the NID of the safeBag
@@ -70,16 +82,20 @@ Other functions return a valid object of the specified type or NULL if an error
L<PKCS12_create(3)>,
L<PKCS12_add_safe(3)>,
-L<PKCS12_add_safes(3)>
+L<PKCS12_add_safes(3)>,
+L<PKCS12_decrypt_secretbag(3)>,
+L<EVP_SKEY_get0_algorithm(3)>
=head1 HISTORY
The functions PKCS12_SAFEBAG_get1_cert_ex() and PKCS12_SAFEBAG_get1_crl_ex() were
added in OpenSSL 3.2.
+PKCS8_PRIV_KEY_INFO_get1_skey() was added in OpenSSL 4.2.
+
=head1 COPYRIGHT
-Copyright 2019-2023 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2019-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
diff --git a/doc/man3/PKCS12_decrypt_secretbag.pod b/doc/man3/PKCS12_decrypt_secretbag.pod
new file mode 100644
index 0000000000..c4ade7637a
--- /dev/null
+++ b/doc/man3/PKCS12_decrypt_secretbag.pod
@@ -0,0 +1,55 @@
+=pod
+
+=head1 NAME
+
+PKCS12_decrypt_secretbag - PKCS12 secret bag decrypt function
+
+=head1 SYNOPSIS
+
+ #include <openssl/pkcs12.h>
+
+ PKCS8_PRIV_KEY_INFO *PKCS12_decrypt_secretbag(const PKCS12_SAFEBAG *bag,
+ const char *pass, int passlen,
+ OSSL_LIB_CTX *ctx,
+ const char *propq);
+
+=head1 DESCRIPTION
+
+PKCS12_decrypt_secretbag() decrypts a PKCS#12 B<secretBag> that contains an
+encrypted PKCS#8 structure (B<NID_pkcs8ShroudedKeyBag> as the bag type within
+B<NID_secretBag>). This is the format used by Java's keytool to store symmetric
+secret keys in PKCS#12 files.
+
+I<bag> is the B<PKCS12_SAFEBAG> to decrypt. I<pass> is the passphrase of
+length I<passlen>. I<ctx> and I<propq> specify the library context and property
+query string for algorithm lookups.
+
+The returned B<PKCS8_PRIV_KEY_INFO> can be passed to
+L<PKCS8_PRIV_KEY_INFO_get1_skey(3)> to obtain an B<EVP_SKEY> symmetric key object.
+
+=head1 RETURN VALUES
+
+PKCS12_decrypt_secretbag() returns a B<PKCS8_PRIV_KEY_INFO> on success or
+NULL on error. The caller must free the returned object with
+PKCS8_PRIV_KEY_INFO_free().
+
+=head1 SEE ALSO
+
+L<PKCS12_SAFEBAG_get1_cert(3)>,
+L<PKCS12_parse(3)>,
+L<PKCS8_decrypt_ex(3)>
+
+=head1 HISTORY
+
+PKCS12_decrypt_secretbag() 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/doc/man3/PKCS12_parse.pod b/doc/man3/PKCS12_parse.pod
index cb63616d10..8b338fc5af 100644
--- a/doc/man3/PKCS12_parse.pod
+++ b/doc/man3/PKCS12_parse.pod
@@ -2,22 +2,63 @@
=head1 NAME
-PKCS12_parse - parse a PKCS#12 structure
+PKCS12_parse_ex, PKCS12_PARSE_CTX_new, PKCS12_PARSE_CTX_free,
+PKCS12_PARSE_CTX_set_pkey, PKCS12_PARSE_CTX_set_cert,
+PKCS12_PARSE_CTX_set_ca, PKCS12_PARSE_CTX_set_skeys,
+PKCS12_parse
+- parse a PKCS#12 structure
=head1 SYNOPSIS
#include <openssl/pkcs12.h>
+ 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);
=head1 DESCRIPTION
-PKCS12_parse() parses a PKCS12 structure.
+PKCS12_parse_ex() parses a PKCS12 structure, extracting the components
+specified by the parse context B<ctx>.
B<p12> is the B<PKCS12> structure to parse. B<pass> is the passphrase to use.
-If successful the private key will be written to B<*pkey>, the corresponding
-certificate to B<*cert> and any additional certificates to B<*ca>.
+B<ctx> is a B<PKCS12_PARSE_CTX> that specifies which components to extract.
+B<libctx> and B<propq> specify the library context and property query string
+for algorithm lookups.
+
+PKCS12_PARSE_CTX_new() allocates a new parse context. All output pointers
+are initially NULL, meaning no components will be extracted until configured
+via the setter functions.
+
+PKCS12_PARSE_CTX_free() frees the parse context. It does not free any
+extracted objects.
+
+PKCS12_PARSE_CTX_set_pkey() configures the context to extract the private key
+into B<*pkey>.
+
+PKCS12_PARSE_CTX_set_cert() configures the context to extract the matching
+certificate into B<*cert>.
+
+PKCS12_PARSE_CTX_set_ca() configures the context to extract additional
+certificates into B<*ca>.
+
+PKCS12_PARSE_CTX_set_skeys() configures the context to extract symmetric
+secret keys into B<*skeys> as a B<STACK_OF(EVP_SKEY)>. All SecretBag structures
+found in the PKCS#12 file will be decrypted and added to the stack.
+This is used for PKCS#12 files containing SecretBag structures, such as
+those created by Java's keytool utility.
+
+PKCS12_parse() is a wrapper around PKCS12_parse_ex() that
+extracts the private key, certificate, and CA certificates. It does not
+support symmetric key extraction or custom library contexts.
=head1 NOTES
@@ -42,8 +83,9 @@ The B<friendlyName> and B<localKeyID> attributes (if present) on each
certificate will be stored in the B<alias> and B<keyid> attributes of the
B<X509> structure.
-If parsing fails B<*pkey>, B<*cert> are set to NULL and if B<ca> is non-NULL
-then B<*ca> is left unchanged.
+If parsing fails B<*pkey>, B<*cert> are set to NULL. If B<ca> is non-NULL
+then B<*ca> is left unchanged. If B<skeys> is configured in the parse context
+then B<*skeys> is also left unchanged on failure.
The parameter B<pass> is interpreted as a string in the UTF-8 encoding. If it
is not valid UTF-8, then it is assumed to be ISO8859-1 instead.
@@ -56,15 +98,41 @@ L<UI_OpenSSL(3)>, for example.
=head1 RETURN VALUES
-PKCS12_parse() returns 1 for success and zero if an error occurred.
+PKCS12_parse_ex() and PKCS12_parse() return 1 for success and zero if an
+error occurred.
The error can be obtained from L<ERR_get_error(3)>
+=head1 EXAMPLES
+
+Extract all components including symmetric keys from a PKCS#12 file:
+
+ PKCS12_PARSE_CTX *ctx = PKCS12_PARSE_CTX_new();
+ EVP_PKEY *pkey = NULL;
+ X509 *cert = NULL;
+ STACK_OF(X509) *ca = NULL;
+ STACK_OF(EVP_SKEY) *skeys = NULL;
+
+ 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 (PKCS12_parse_ex(p12, password, ctx, NULL, NULL)) {
+ /* use pkey, cert, ca, skeys */
+ }
+
+ EVP_PKEY_free(pkey);
+ X509_free(cert);
+ OSSL_STACK_OF_X509_free(ca);
+ sk_EVP_SKEY_pop_free(skeys, EVP_SKEY_free);
+ PKCS12_PARSE_CTX_free(ctx);
+
=head1 BUGS
-Only a single private key and corresponding certificate is returned by this
-function. More complex PKCS#12 files with multiple private keys will only
-return the first match.
+Only a single private key and corresponding certificate is returned by
+PKCS12_parse() and PKCS12_parse_ex(). More complex PKCS#12 files with
+multiple private keys will only return the first match.
Only B<friendlyName> and B<localKeyID> attributes are currently stored in
certificates. Other attributes are discarded.
@@ -76,6 +144,13 @@ Attributes currently cannot be stored in the private key B<EVP_PKEY> structure.
L<d2i_PKCS12(3)>,
L<passphrase-encoding(7)>
+=head1 HISTORY
+
+PKCS12_parse_ex(), PKCS12_PARSE_CTX_new(), PKCS12_PARSE_CTX_free(),
+PKCS12_PARSE_CTX_set_pkey(), PKCS12_PARSE_CTX_set_cert(),
+PKCS12_PARSE_CTX_set_ca(), and PKCS12_PARSE_CTX_set_skeys()
+were added in OpenSSL 4.2.
+
=head1 COPYRIGHT
Copyright 2002-2026 The OpenSSL Project Authors. All Rights Reserved.