Skip to content

Commit

Permalink
support setting more PKCS12 serialization encryption options
Browse files Browse the repository at this point in the history
This is limited support, but makes it possible to set two different PBES
choices as well as set KDF rounds and MAC algorithm
  • Loading branch information
reaperhulk committed Sep 5, 2022
1 parent abb1f54 commit 342534a
Show file tree
Hide file tree
Showing 6 changed files with 442 additions and 20 deletions.
128 changes: 114 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.

.. 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")
... )
>>> 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.

.. 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.


PKCS7
~~~~~

Expand Down Expand Up @@ -841,16 +897,41 @@ 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")
... )
>>> pkcs12.serialize_key_and_certificates(
... b"friendlyname", key, None, None, encryption
... )
b'...'

.. 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 +1103,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 +1115,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)

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
):
# 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

0 comments on commit 342534a

Please sign in to comment.