Skip to content

Commit 1742975

Browse files
reaperhulkalex
andauthored
support setting more PKCS12 serialization encryption options (#7560)
* 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 * Apply suggestions from code review Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> * review feedback redux * Update docs/hazmat/primitives/asymmetric/serialization.rst Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com> Co-authored-by: Alex Gaynor <alex.gaynor@gmail.com>
1 parent abb1f54 commit 1742975

File tree

6 files changed

+444
-20
lines changed

6 files changed

+444
-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 less secure, 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+
... hmac_hash(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-CBC as the cipher.
631+
632+
.. attribute:: PBESv2SHA256AndAES256CBC
633+
634+
PBESv2 using SHA256 as the KDF PRF and AES256-CBC as the cipher. This
635+
is 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+
... hmac_hash(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:: hmac_hash(algorithm)
1139+
1140+
Set the hash algorithm to use within the MAC 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._hmac_hash 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._hmac_hash
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)