From c66801a8516391ca31375631a5b398f331957d4c Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Fri, 29 Jul 2022 17:40:07 +0200 Subject: [PATCH] Provide a way to export PKCS12 in a way compatible with major OS (closes #7293) --- .../primitives/asymmetric/serialization.rst | 9 ++++++ .../hazmat/backends/openssl/backend.py | 29 +++++++++++++++++++ .../hazmat/primitives/_serialization.py | 12 ++++++++ .../primitives/serialization/__init__.py | 2 ++ tests/hazmat/primitives/test_pkcs12.py | 1 + 5 files changed, 53 insertions(+) diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst index e547f8359942..5a3f55389eb1 100644 --- a/docs/hazmat/primitives/asymmetric/serialization.rst +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -1002,6 +1002,15 @@ Serialization Encryption Types :param bytes password: The password to use for encryption. +.. class:: PKCS12CompatibilityEncryption(password) + + Encrypt using 3DES and SHA1 for a given key. + This is an option that should be only used to allow exporting PKCS12 + to devices/software that does not support stronger encryption of PKCS12 + like macOS 15.x or Android 12. + + :param bytes password: The password to use for encryption. + .. class:: NoEncryption Do not encrypt. diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 8311c3f8fa15..efa2ae9df61e 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -2247,6 +2247,7 @@ 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 ): @@ -2261,6 +2262,20 @@ def serialize_key_and_certificates_to_pkcs12( # Did we mention how lousy PKCS12 encryption is? mac_iter = 1 password = encryption_algorithm.password + mac_alg = self._ffi.NULL + elif isinstance( + encryption_algorithm, serialization.PKCS12CompatibilityEncryption + ): + # This is currently mostly identical with BestAvailableEncryption + # but is meant to stay 3DES/SHA1 to be able to support operating + # systems like Android <= 12/13 and macOS <= 15 that do not support + # anything stronger + nid_cert = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + nid_key = self._lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC + mac_iter = 1 + pkcs12_iter = 20000 + password = encryption_algorithm.password + mac_alg = self._evp_md_non_null_from_algorithm(hashes.SHA1) else: raise ValueError("Unsupported key encryption type") @@ -2310,6 +2325,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) diff --git a/src/cryptography/hazmat/primitives/_serialization.py b/src/cryptography/hazmat/primitives/_serialization.py index 160a6b89c089..fc68421dbb58 100644 --- a/src/cryptography/hazmat/primitives/_serialization.py +++ b/src/cryptography/hazmat/primitives/_serialization.py @@ -51,5 +51,17 @@ def __init__(self, password: bytes): self.password = password +class PKCS12CompatibilityEncryption(KeySerializationEncryption): + """ " + Provides the most compatible encryption using 3DES and SHA1 for PKCS12. + """ + + def __init__(self, password): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + class NoEncryption(KeySerializationEncryption): pass diff --git a/src/cryptography/hazmat/primitives/serialization/__init__.py b/src/cryptography/hazmat/primitives/serialization/__init__.py index 1e0174b033de..96cc7ad35635 100644 --- a/src/cryptography/hazmat/primitives/serialization/__init__.py +++ b/src/cryptography/hazmat/primitives/serialization/__init__.py @@ -8,6 +8,7 @@ Encoding, KeySerializationEncryption, NoEncryption, + PKCS12CompatibilityEncryption, ParameterFormat, PrivateFormat, PublicFormat, @@ -41,5 +42,6 @@ "ParameterFormat", "KeySerializationEncryption", "BestAvailableEncryption", + "PKCS12CompatibilityEncryption", "NoEncryption", ] diff --git a/tests/hazmat/primitives/test_pkcs12.py b/tests/hazmat/primitives/test_pkcs12.py index ddb0c648d73b..1849cbc5e20c 100644 --- a/tests/hazmat/primitives/test_pkcs12.py +++ b/tests/hazmat/primitives/test_pkcs12.py @@ -341,6 +341,7 @@ class TestPKCS12Creation: ("algorithm", "password"), [ (serialization.BestAvailableEncryption(b"password"), b"password"), + (serialization.PKCS12CompatibilityEncryption(b"pass"), b"pass"), (serialization.NoEncryption(), None), ], )