Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support setting more PKCS12 serialization encryption options #7560

Merged
merged 4 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 113 additions & 14 deletions docs/hazmat/primitives/asymmetric/serialization.rst
Expand Up @@ -491,12 +491,20 @@ file suffix.

.. versionadded:: 3.0

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

.. warning::

PKCS12 encryption is not secure and should not be used as a security
mechanism. Wrap a PKCS12 blob in a more secure envelope if you need
to store or send it safely. Encryption is provided for compatibility
reasons only.
PKCS12 encryption is typically not secure and should not be used as a
security mechanism. Wrap a PKCS12 blob in a more secure envelope if you
need to store or send it safely.

Serialize a PKCS12 blob.

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

:return bytes: Serialized PKCS12.

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives.serialization import BestAvailableEncryption, load_pem_private_key, pkcs12
>>> cert = x509.load_pem_x509_certificate(ca_cert)
>>> key = load_pem_private_key(ca_key, None)
>>> p12 = pkcs12.serialize_key_and_certificates(
... b"friendlyname", key, cert, None, BestAvailableEncryption(b"password")
... )

This example uses an ``encryption_builder()`` to create a PKCS12 with more
compatible, but substantially worse, encryption.
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved

.. doctest::

>>> from cryptography import x509
>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.serialization import PrivateFormat, load_pem_private_key, pkcs12
>>> encryption = (
... PrivateFormat.PKCS12.encryption_builder().
... kdf_rounds(50000).
... key_cert_algorithm(pkcs12.PBES.PBESv1SHA1And3KeyTripleDESCBC).
... mac_algorithm(hashes.SHA1()).build(b"my password")
... )
Copy link

@schwabe schwabe Sep 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if the parenthesis here serve any purpose. I think Python just ignores them but some might confuse them to put the encryption into a tuple. But then again if you confuse that, you probably should not be playing with these things ...

Edit, nevermind. It allows you to skip all the \.

>>> cert = x509.load_pem_x509_certificate(ca_cert)
>>> key = load_pem_private_key(ca_key, None)
>>> p12 = pkcs12.serialize_key_and_certificates(
... b"friendlyname", key, None, None, encryption
... )

.. class:: PKCS12Certificate

.. versionadded:: 36.0
Expand Down Expand Up @@ -579,6 +617,24 @@ file suffix.
A list of :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PKCS12Certificate`
instances.

.. class:: PBES

.. versionadded:: 38.0.0

An enumeration of password-based encryption schemes used in PKCS12. These
values are used with
:class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryptionBuilder`.

.. attribute:: PBESv1SHA1And3KeyTripleDESCBC

PBESv1 using SHA1 as the KDF PRF and 3-key triple DES as the cipher.
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved

.. attribute:: PBESv2SHA256AndAES256CBC

PBESv2 using SHA256 as the KDF PRF and AES256 as the cipher. This is
only supported on OpenSSL 3.0.0 or newer.
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved


PKCS7
~~~~~

Expand Down Expand Up @@ -841,16 +897,40 @@ Serialization Formats
...
-----END OPENSSH PRIVATE KEY-----

.. attribute:: PKCS12

.. versionadded:: 38.0.0

The PKCS#12 format is a binary format used to store private keys and
certificates. This attribute is used in conjunction with
``encryption_builder()`` to allow control of the encryption algorithm
and parameters.

.. doctest::

>>> from cryptography.hazmat.primitives import hashes
>>> from cryptography.hazmat.primitives.serialization import PrivateFormat, pkcs12
>>> encryption = (
... PrivateFormat.PKCS12.encryption_builder().
... kdf_rounds(50000).
... key_cert_algorithm(pkcs12.PBES.PBESv2SHA256AndAES256CBC).
... mac_algorithm(hashes.SHA256()).build(b"my password")
... )
>>> p12 = pkcs12.serialize_key_and_certificates(
... b"friendlyname", key, None, None, encryption
... )

.. method:: encryption_builder()

.. versionadded:: 38.0.0

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

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

:returns KeySerializationEncryptionBuilder: A new builder.
:returns: A new instance of :class:`KeySerializationEncryptionBuilder`

.. doctest::

Expand Down Expand Up @@ -1022,7 +1102,8 @@ Serialization Encryption Types

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

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

Expand All @@ -1033,25 +1114,43 @@ Serialization Encryption Types

.. class:: KeySerializationEncryptionBuilder

A builder that can be used to configure how key data is encrypted. To
create one, call :meth:`PrivateFormat.encryption_builder`.
.. versionadded:: 38.0.0

A builder that can be used to configure how data is encrypted. To
create one, call :meth:`PrivateFormat.encryption_builder`. Different
serialization types will support different options on this builder.

.. method:: kdf_rounds(rounds)

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

:param int rounds: Number of rounds.
:returns KeySerializationEncryptionBuilder: A new builder.

.. method:: key_cert_algorithm(algorithm)

Set the encryption algorithm to use when encrypting the key and
certificate in a PKCS12 structure.

:param algorithm: A value from the :class:`~cryptography.hazmat.primitives.serialization.pkcs12.PBES`
enumeration.

.. method:: mac_algorithm(algorithm)
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved

Set the MAC algorithm to use for a PKCS12 structure.

:param algorithm: An instance of a
:class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`

.. method:: build(password)

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

:param bytes password: The password.
:returns KeySerializationEncryption: A key key serialization
encryption that can be passed to ``private_bytes`` methods.
:returns: A :class:`KeySerializationEncryption` encryption object
that can be passed to methods like ``private_bytes`` or
:func:`~cryptography.hazmat.primitives.serialization.pkcs12.serialize_key_and_certificates`.

.. _`a bug in Firefox`: https://bugzilla.mozilla.org/show_bug.cgi?id=773111
.. _`PKCS3`: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf
Expand Down
76 changes: 73 additions & 3 deletions src/cryptography/hazmat/backends/openssl/backend.py
Expand Up @@ -116,6 +116,7 @@
from cryptography.hazmat.primitives.kdf import scrypt
from cryptography.hazmat.primitives.serialization import pkcs7, ssh
from cryptography.hazmat.primitives.serialization.pkcs12 import (
PBES,
PKCS12Certificate,
PKCS12KeyAndCertificates,
_ALLOWED_PKCS12_TYPES,
Expand Down Expand Up @@ -2263,20 +2264,75 @@ def serialize_key_and_certificates_to_pkcs12(
nid_key = -1
pkcs12_iter = 0
mac_iter = 0
mac_alg = self._ffi.NULL
elif isinstance(
encryption_algorithm, serialization.BestAvailableEncryption
):
# PKCS12 encryption is hopeless trash and can never be fixed.
# This is the least terrible option.
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
# OpenSSL 3 supports PBESv2, but Libre and Boring do not, so
# we use PBESv1 with 3DES on the older paths.
if self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
nid_cert = self._lib.NID_aes_256_cbc
nid_key = self._lib.NID_aes_256_cbc
else:
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
# At least we can set this higher than OpenSSL's default
pkcs12_iter = 20000
# mac_iter chosen for compatibility reasons, see:
# https://www.openssl.org/docs/man1.1.1/man3/PKCS12_create.html
# Did we mention how lousy PKCS12 encryption is?
mac_iter = 1
# MAC algorithm can only be set on OpenSSL 3.0.0+
mac_alg = self._ffi.NULL
password = encryption_algorithm.password
elif (
isinstance(
encryption_algorithm, serialization._KeySerializationEncryption
)
and encryption_algorithm._format
is serialization.PrivateFormat.PKCS12
):
reaperhulk marked this conversation as resolved.
Show resolved Hide resolved
# Default to OpenSSL's defaults. Behavior will vary based on the
# version of OpenSSL cryptography is compiled against.
nid_cert = 0
nid_key = 0
# Use the default iters we use in best available
pkcs12_iter = 20000
# See the Best Available comment for why this is 1
mac_iter = 1
password = encryption_algorithm.password
keycertalg = encryption_algorithm._key_cert_algorithm
if keycertalg is PBES.PBESv1SHA1And3KeyTripleDESCBC:
nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC
elif keycertalg is PBES.PBESv2SHA256AndAES256CBC:
if not self._lib.CRYPTOGRAPHY_OPENSSL_300_OR_GREATER:
raise UnsupportedAlgorithm(
"PBESv2 is not supported by this version of OpenSSL"
)
nid_cert = self._lib.NID_aes_256_cbc
nid_key = self._lib.NID_aes_256_cbc
else:
assert keycertalg is None
# We use OpenSSL's defaults

if encryption_algorithm._mac_algorithm is not None:
if not self._lib.Cryptography_HAS_PKCS12_SET_MAC:
raise UnsupportedAlgorithm(
"Setting MAC algorithm is not supported by this "
"version of OpenSSL."
)
mac_alg = self._evp_md_non_null_from_algorithm(
encryption_algorithm._mac_algorithm
)
self.openssl_assert(mac_alg != self._ffi.NULL)
else:
mac_alg = self._ffi.NULL

if encryption_algorithm._kdf_rounds is not None:
pkcs12_iter = encryption_algorithm._kdf_rounds

else:
raise ValueError("Unsupported key encryption type")

Expand Down Expand Up @@ -2326,6 +2382,20 @@ def serialize_key_and_certificates_to_pkcs12(
0,
)

if (
self._lib.Cryptography_HAS_PKCS12_SET_MAC
and mac_alg != self._ffi.NULL
):
self._lib.PKCS12_set_mac(
p12,
password_buf,
-1,
self._ffi.NULL,
0,
mac_iter,
mac_alg,
)

self.openssl_assert(p12 != self._ffi.NULL)
p12 = self._ffi.gc(p12, self._lib.PKCS12_free)

Expand Down