Skip to content

Commit 9d2567b

Browse files
committed
support setting more PKCS12 serialization encryption options
This is limited support, but makes it possible to set two different PBES choices as well as set KDF rounds and MAC algorithm
1 parent abb1f54 commit 9d2567b

File tree

6 files changed

+441
-20
lines changed

6 files changed

+441
-20
lines changed

docs/hazmat/primitives/asymmetric/serialization.rst

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -491,12 +491,20 @@ file suffix.
491491

492492
.. versionadded:: 3.0
493493

494+
.. note::
495+
With OpenSSL 3.0.0+ the defaults for encryption when serializing PKCS12
496+
have changed and some versions of Windows and macOS will not be able to
497+
read the new format. Maximum compatibility can be achieved by using
498+
``SHA1`` for MAC algorithm and
499+
:attr:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC`
500+
for encryption algorithm as seen in the example below. However, users
501+
should avoid this unless required for compatibility.
502+
494503
.. warning::
495504

496-
PKCS12 encryption is not secure and should not be used as a security
497-
mechanism. Wrap a PKCS12 blob in a more secure envelope if you need
498-
to store or send it safely. Encryption is provided for compatibility
499-
reasons only.
505+
PKCS12 encryption is typically not secure and should not be used as a
506+
security mechanism. Wrap a PKCS12 blob in a more secure envelope if you
507+
need to store or send it safely.
500508

501509
Serialize a PKCS12 blob.
502510

@@ -535,11 +543,41 @@ file suffix.
535543
:param encryption_algorithm: The encryption algorithm that should be used
536544
for the key and certificate. An instance of an object conforming to the
537545
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption`
538-
interface. PKCS12 encryption is **very weak** and should not be used
539-
as a security boundary.
546+
interface. PKCS12 encryption is typically **very weak** and should not
547+
be used as a security boundary.
540548

541549
:return bytes: Serialized PKCS12.
542550

551+
.. doctest::
552+
553+
>>> from cryptography import x509
554+
>>> from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, load_pem_private_key, pkcs12
555+
>>> cert = x509.load_pem_x509_certificate(ca_cert)
556+
>>> key = load_pem_private_key(ca_key, None)
557+
>>> p12 = pkcs12.serialize_key_and_certificates(
558+
... b"friendlyname", key, cert, None, BestAvailableEncryption(b"password")
559+
... )
560+
561+
This example uses an ``encryption_builder()`` to create a PKCS12 with more
562+
compatible, but substantially worse, encryption.
563+
564+
.. doctest::
565+
566+
>>> from cryptography import x509
567+
>>> from cryptography.hazmat.primitives import hashes
568+
>>> from cryptography.hazmat.primitives.serialization import PrivateFormat, load_pem_private_key, pkcs12
569+
>>> encryption = (
570+
... PrivateFormat.PKCS12.encryption_builder().
571+
... kdf_rounds(50000).
572+
... key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC).
573+
... mac_algorithm(hashes.SHA1()).build(b"my password")
574+
... )
575+
>>> cert = x509.load_pem_x509_certificate(ca_cert)
576+
>>> key = load_pem_private_key(ca_key, None)
577+
>>> p12 = pkcs12.serialize_key_and_certificates(
578+
... b"friendlyname", key, None, None, encryption
579+
... )
580+
543581
.. class:: PKCS12Certificate
544582

545583
.. versionadded:: 36.0
@@ -579,6 +617,24 @@ file suffix.
579617
A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate`
580618
instances.
581619

620+
.. class:: PBES
621+
622+
.. versionadded:: 38.0.0
623+
624+
An enumeration of password-based encryption schemes used in PKCS12. These
625+
values are used with
626+
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryptionBuilder`.
627+
628+
.. attribute:: PBESv1SHA1And3KeyTripleDESCBC
629+
630+
PBESv1 using SHA1 as the KDF PRF and 3-key triple DES as the cipher.
631+
632+
.. attribute:: PBESv2SHA256AndAES256CBC
633+
634+
PBESv2 using SHA256 as the KDF PRF and AES256 as the cipher. This is
635+
only supported on OpenSSL 3.0.0 or newer.
636+
637+
582638
PKCS7
583639
~~~~~
584640

@@ -841,16 +897,40 @@ Serialization Formats
841897
...
842898
-----END OPENSSH PRIVATE KEY-----
843899

900+
.. attribute:: PKCS12
901+
902+
.. versionadded:: 38.0.0
903+
904+
The PKCS#12 format is a binary format used to store private keys and
905+
certificates. This attribute is used in conjunction with
906+
``encryption_builder()`` to allow control of the encryption algorithm
907+
and parameters.
908+
909+
.. doctest::
910+
911+
>>> from cryptography.hazmat.primitives import hashes
912+
>>> from cryptography.hazmat.primitives.serialization import PrivateFormat, pkcs12
913+
>>> encryption = (
914+
... PrivateFormat.PKCS12.encryption_builder().
915+
... kdf_rounds(50000).
916+
... key_cert_algorithm(pkcs12.PBES.PBESv2SHA256AndAES256CBC).
917+
... mac_algorithm(hashes.SHA256()).build(b"my password")
918+
... )
919+
>>> p12 = pkcs12.serialize_key_and_certificates(
920+
... b"friendlyname", key, None, None, encryption
921+
... )
922+
844923
.. method:: encryption_builder()
845924

846925
.. versionadded:: 38.0.0
847926

848927
Returns a builder for configuring how values are encrypted with this
849-
format.
928+
format. You must call this method on an element of the enumeration.
929+
For example, ``PrivateFormat.OpenSSH.encryption_builder()``.
850930

851931
For most use cases, :class:`BestAvailableEncryption` is preferred.
852932

853-
:returns KeySerializationEncryptionBuilder: A new builder.
933+
:returns: A new instance of :class:`KeySerializationEncryptionBuilder`
854934

855935
.. doctest::
856936

@@ -1022,7 +1102,8 @@ Serialization Encryption Types
10221102

10231103
Encrypt using the best available encryption for a given key.
10241104
This is a curated encryption choice and the algorithm may change over
1025-
time.
1105+
time. The encryption algorithm may vary based on which version of OpenSSL
1106+
the library is compiled against.
10261107

10271108
:param bytes password: The password to use for encryption.
10281109

@@ -1033,25 +1114,43 @@ Serialization Encryption Types
10331114

10341115
.. class:: KeySerializationEncryptionBuilder
10351116

1036-
A builder that can be used to configure how key data is encrypted. To
1037-
create one, call :meth:`PrivateFormat.encryption_builder`.
1117+
.. versionadded:: 38.0.0
1118+
1119+
A builder that can be used to configure how data is encrypted. To
1120+
create one, call :meth:`PrivateFormat.encryption_builder`. Different
1121+
serialization types will support different options on this builder.
10381122

10391123
.. method:: kdf_rounds(rounds)
10401124

10411125
Set the number of rounds the Key Derivation Function should use. The
10421126
meaning of the number of rounds varies on the KDF being used.
10431127

10441128
:param int rounds: Number of rounds.
1045-
:returns KeySerializationEncryptionBuilder: A new builder.
1129+
1130+
.. method:: key_cert_algorithm(algorithm)
1131+
1132+
Set the encryption algorithm to use when encrypting the key and
1133+
certificate in a PKCS12 structure.
1134+
1135+
:param algorithm: A value from the :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES`
1136+
enumeration.
1137+
1138+
.. method:: mac_algorithm(algorithm)
1139+
1140+
Set the MAC algorithm to use for a PKCS12 structure.
1141+
1142+
:param algorithm: An instance of a
1143+
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`
10461144

10471145
.. method:: build(password)
10481146

10491147
Turns the builder into an instance of
10501148
:class:`KeySerializationEncryption` with a given password.
10511149

10521150
:param bytes password: The password.
1053-
:returns KeySerializationEncryption: A key key serialization
1054-
encryption that can be passed to ``private_bytes`` methods.
1151+
:returns: A :class:`KeySerializationEncryption` encryption object
1152+
that can be passed to methods like ``private_bytes`` or
1153+
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates`.
10551154

10561155
.. _`a bug in Firefox`: https://bugzilla.mozilla.org/show_bug.cgi?id=773111
10571156
.. _`PKCS3`: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf

src/cryptography/hazmat/backends/openssl/backend.py

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@
116116
from cryptography.hazmat.primitives.kdf import scrypt
117117
from cryptography.hazmat.primitives.serialization import pkcs7, ssh
118118
from cryptography.hazmat.primitives.serialization.pkcs12 import (
119+
PBES,
119120
PKCS12Certificate,
120121
PKCS12KeyAndCertificates,
121122
_ALLOWED_PKCS12_TYPES,
@@ -2263,20 +2264,75 @@ def serialize_key_and_certificates_to_pkcs12(
22632264
nid_key = -1
22642265
pkcs12_iter = 0
22652266
mac_iter = 0
2267+
mac_alg = self._ffi.NULL
22662268
elif isinstance(
22672269
encryption_algorithm, serialization.BestAvailableEncryption
22682270
):
22692271
# PKCS12 encryption is hopeless trash and can never be fixed.
2270-
# This is the least terrible option.
2271-
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
2272-
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
2272+
# OpenSSL 3 supports PBESv2, but Libre and Boring do not, so
2273+
# we use PBESv1 with 3DES on the older paths.
2274+
if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
2275+
nid_cert = self._lib.NID_aes_256_cbc
2276+
nid_key = self._lib.NID_aes_256_cbc
2277+
else:
2278+
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
2279+
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
22732280
# At least we can set this higher than OpenSSL's default
22742281
pkcs12_iter = 20000
22752282
# mac_iter chosen for compatibility reasons, see:
22762283
# https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html
22772284
# Did we mention how lousy PKCS12 encryption is?
22782285
mac_iter = 1
2286+
# MAC algorithm can only be set on OpenSSL 3.0.0+
2287+
mac_alg = self._ffi.NULL
22792288
password = encryption_algorithm.password
2289+
elif (
2290+
isinstance(
2291+
encryption_algorithm, serialization._KeySerializationEncryption
2292+
)
2293+
and encryption_algorithm._format
2294+
is serialization.PrivateFormat.PKCS12
2295+
):
2296+
# Default to OpenSSL's defaults. Behavior will vary based on the
2297+
# version of OpenSSL cryptography is compiled against.
2298+
nid_cert = 0
2299+
nid_key = 0
2300+
# Use the default iters we use in best available
2301+
pkcs12_iter = 20000
2302+
# See the Best Available comment for why this is 1
2303+
mac_iter = 1
2304+
password = encryption_algorithm.password
2305+
keycertalg = encryption_algorithm._key_cert_algorithm
2306+
if keycertalg is PBES.PBESv1SHA1And3KeyTripleDESCBC:
2307+
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
2308+
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
2309+
elif keycertalg is PBES.PBESv2SHA256AndAES256CBC:
2310+
if not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
2311+
raise UnsupportedAlgorithm(
2312+
"PBESv2 is not supported by this version of OpenSSL"
2313+
)
2314+
nid_cert = self._lib.NID_aes_256_cbc
2315+
nid_key = self._lib.NID_aes_256_cbc
2316+
else:
2317+
assert keycertalg is None
2318+
# We use OpenSSL's defaults
2319+
2320+
if encryption_algorithm._mac_algorithm is not None:
2321+
if not self._lib.Cryptography_HAS_PKCS12_SET_MAC:
2322+
raise UnsupportedAlgorithm(
2323+
"Setting MAC algorithm is not supported by this "
2324+
"version of OpenSSL."
2325+
)
2326+
mac_alg = self._evp_md_non_null_from_algorithm(
2327+
encryption_algorithm._mac_algorithm
2328+
)
2329+
self.openssl_assert(mac_alg != self._ffi.NULL)
2330+
else:
2331+
mac_alg = self._ffi.NULL
2332+
2333+
if encryption_algorithm._kdf_rounds is not None:
2334+
pkcs12_iter = encryption_algorithm._kdf_rounds
2335+
22802336
else:
22812337
raise ValueError("Unsupported key encryption type")
22822338

@@ -2326,6 +2382,20 @@ def serialize_key_and_certificates_to_pkcs12(
23262382
0,
23272383
)
23282384

2385+
if (
2386+
self._lib.Cryptography_HAS_PKCS12_SET_MAC
2387+
and mac_alg != self._ffi.NULL
2388+
):
2389+
self._lib.PKCS12_set_mac(
2390+
p12,
2391+
password_buf,
2392+
-1,
2393+
self._ffi.NULL,
2394+
0,
2395+
mac_iter,
2396+
mac_alg,
2397+
)
2398+
23292399
self.openssl_assert(p12 != self._ffi.NULL)
23302400
p12 = self._ffi.gc(p12, self._lib.PKCS12_free)
23312401

0 commit comments

Comments
 (0)