From d283063ba3b2fd71fd25693f51900e304052a8bf Mon Sep 17 00:00:00 2001 From: Matthias Valvekens Date: Fri, 15 Mar 2024 22:52:32 +0100 Subject: [PATCH] Remove all oscrypto calls pyca/cryptography is now a required dependency and the only CryptoBackend. oscrypto is also no longer used for loading keys (at the time of writing it breaks badly on systems with openssl3) --- README.md | 2 +- certomancer/cli.py | 13 -- certomancer/crypto_utils.py | 127 +++--------------- certomancer/integrations/animator.py | 13 +- .../animator_templates/arch_summary.html | 94 +++++++------ certomancer/registry/pki_arch.py | 3 +- docs/deploy.md | 2 +- example_plugin/encrypt_echo.py | 20 ++- pyproject.toml | 7 +- tests/conftest.py | 26 ---- tests/test_animator.py | 35 +++-- tests/test_certs.py | 14 +- tests/test_services.py | 32 ++--- 13 files changed, 122 insertions(+), 266 deletions(-) diff --git a/README.md b/README.md index 24feab5..1f22a11 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Requires Python 3.7 or later. Certomancer is [available on PyPI](https://pypi.org/project/certomancer/). See `example.yml` for an example config file. ```bash -$ pip install 'certomancer[web-api,pkcs12]' +$ pip install 'certomancer[web-api]' $ certomancer --config example.yml animate ``` diff --git a/certomancer/cli.py b/certomancer/cli.py index af14b52..0e4d6da 100644 --- a/certomancer/cli.py +++ b/certomancer/cli.py @@ -11,7 +11,6 @@ from ._asn1_types import register_extensions from .config_utils import ConfigurationError -from .crypto_utils import pyca_cryptography_present from .registry import ( ArchLabel, AttributeCertificateSpec, @@ -189,12 +188,6 @@ def mass_summon( ): cfg: CertomancerConfig = next(ctx.obj['config']) pki_arch = cfg.get_pki_arch(ArchLabel(architecture)) - if not no_pfx and not pyca_cryptography_present(): - no_pfx = True - logger.warning( - "pyca/cryptography not installed, no PFX files will be created" - ) - if pfx_pass is not None: pfx_pass_bytes = pfx_pass.encode('utf8') else: @@ -258,12 +251,6 @@ def summon( ): cfg: CertomancerConfig = next(ctx.obj['config']) pki_arch = cfg.get_pki_arch(ArchLabel(architecture)) - if as_pfx and not pyca_cryptography_present(): - as_pfx = False - logger.warning( - "pyca/cryptography not installed, no PFX files will be created" - ) - output_is_binary = as_pfx or no_pem if ( diff --git a/certomancer/crypto_utils.py b/certomancer/crypto_utils.py index 060554c..d23c508 100644 --- a/certomancer/crypto_utils.py +++ b/certomancer/crypto_utils.py @@ -1,9 +1,9 @@ -import hashlib import logging from typing import Optional, Tuple from asn1crypto import algos, keys, pem, x509 from asn1crypto.keys import PublicKeyInfo +from cryptography.hazmat.primitives import serialization logger = logging.getLogger(__name__) @@ -31,83 +31,23 @@ def optimal_pss_params( raise NotImplementedError -class OscryptoBackend(CryptoBackend): - def load_private_key( - self, key_bytes: bytes, password: Optional[bytes] - ) -> Tuple[keys.PrivateKeyInfo, keys.PublicKeyInfo]: - from oscrypto import asymmetric - from oscrypto import keys as oskeys - - private = oskeys.parse_private(key_bytes, password=password) - if private.algorithm == 'rsassa_pss': - loaded, public = _oscrypto_hacky_load_pss_exclusive_key(private) - else: - loaded = asymmetric.load_private_key(private) - public = loaded.public_key.asn1 - return private, public - - def load_public_key(self, key_bytes: bytes) -> keys.PublicKeyInfo: - from oscrypto import keys as oskeys - - return oskeys.parse_public(key_bytes) - - def generic_sign( - self, - private_key: keys.PrivateKeyInfo, - tbs_bytes: bytes, - sd_algo: algos.SignedDigestAlgorithm, - ) -> bytes: - from oscrypto import asymmetric - - pk_algo = private_key.algorithm - loaded_key = None - if pk_algo == 'rsa': - if sd_algo.signature_algo == 'rsassa_pss': - sign_fun = asymmetric.rsa_pss_sign - else: - sign_fun = asymmetric.rsa_pkcs1v15_sign - elif pk_algo == 'rsassa_pss': - loaded_key = _oscrypto_hacky_load_pss_exclusive_key(private_key)[0] - sign_fun = asymmetric.rsa_pss_sign - elif pk_algo == 'ec': - sign_fun = asymmetric.ecdsa_sign - elif pk_algo == 'dsa': - sign_fun = asymmetric.dsa_sign - else: - raise NotImplementedError( - f"The signing mechanism '{pk_algo}' is not supported." - ) - if loaded_key is None: - loaded_key = asymmetric.load_private_key(private_key) - return sign_fun(loaded_key, tbs_bytes, sd_algo.hash_algo) - - def optimal_pss_params(self, key: PublicKeyInfo, digest_algo: str): - key_algo = key.algorithm - if key_algo == 'rsassa_pss': - logger.warning( - "You seem to be using an RSA key that has been marked as " - "RSASSA-PSS exclusive. If it has non-null parameters, these " - "WILL be disregarded by the signer, since oscrypto doesn't " - "currently support RSASSA-PSS with arbitrary parameters." - ) - # replicate default oscrypto PSS settings - salt_len = len(getattr(hashlib, digest_algo)().digest()) - return algos.RSASSAPSSParams( - { - 'hash_algorithm': algos.DigestAlgorithm( - {'algorithm': digest_algo} - ), - 'mask_gen_algorithm': algos.MaskGenAlgorithm( - { - 'algorithm': 'mgf1', - 'parameters': algos.DigestAlgorithm( - {'algorithm': digest_algo} - ), - } - ), - 'salt_length': salt_len, - } +def _load_private_key_from_pemder_data( + key_bytes: bytes, passphrase: Optional[bytes] +) -> keys.PrivateKeyInfo: + load_fun = ( + serialization.load_pem_private_key + if pem.detect(key_bytes) + else serialization.load_der_private_key + ) + + private_key = load_fun(key_bytes, password=passphrase) + return keys.PrivateKeyInfo.load( + private_key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption(), ) + ) class PycaCryptographyBackend(CryptoBackend): @@ -115,13 +55,8 @@ def load_private_key( self, key_bytes: bytes, password: Optional[bytes] ) -> Tuple[keys.PrivateKeyInfo, keys.PublicKeyInfo]: from cryptography.hazmat.primitives import serialization - from oscrypto import keys as oskeys - # use oscrypto parser here to parse the key to a PrivateKeyInfo object - # (It handles unarmoring/decryption/... without worrying about the - # key type, while load_der/pem_private_key would fail to process - # PSS-exclusive keys) - priv_key_info = oskeys.parse_private(key_bytes, password) + priv_key_info = _load_private_key_from_pemder_data(key_bytes, password) assert isinstance(priv_key_info, keys.PrivateKeyInfo) if priv_key_info.algorithm == 'rsassa_pss': # these keys can't be loaded directly in pyca/cryptography, @@ -281,31 +216,7 @@ def pyca_cryptography_present() -> bool: return False -def _oscrypto_hacky_load_pss_exclusive_key(private: keys.PrivateKeyInfo): - from oscrypto import asymmetric - - # HACK to load PSS-exclusive RSA keys in oscrypto - # Don't ever do this in production code! - algo_copy = private['private_key_algorithm'].native - private_copy = keys.PrivateKeyInfo.load(private.dump()) - # set the algorithm to "generic RSA" - private_copy['private_key_algorithm'] = {'algorithm': 'rsa'} - loaded_key = asymmetric.load_private_key(private_copy) - public = loaded_key.public_key.asn1 - public['algorithm'] = algo_copy - public._algorithm = None - return loaded_key, public - - -def _select_default_crypto_backend() -> CryptoBackend: - # pyca/cryptography required for EdDSA certs - if pyca_cryptography_present(): - return PycaCryptographyBackend() - else: - return OscryptoBackend() - - -CRYPTO_BACKEND: CryptoBackend = _select_default_crypto_backend() +CRYPTO_BACKEND: CryptoBackend = PycaCryptographyBackend() def generic_sign( diff --git a/certomancer/integrations/animator.py b/certomancer/integrations/animator.py index c781693..f75e3a0 100644 --- a/certomancer/integrations/animator.py +++ b/certomancer/integrations/animator.py @@ -18,7 +18,6 @@ from werkzeug.wrappers import Request, Response from certomancer.config_utils import ConfigurationError -from certomancer.crypto_utils import pyca_cryptography_present from certomancer.registry import ( ArchLabel, AttributeCertificateSpec, @@ -36,8 +35,6 @@ logger = logging.getLogger(__name__) -pfx_possible = pyca_cryptography_present() - def _now(): return datetime.now(tz=tzlocal.get_localzone()) @@ -70,7 +67,7 @@ class AnimatorCertInfo: @staticmethod def gather_cert_info(pki_arch: PKIArchitecture): def _for_cert(spec: CertificateSpec): - pfx = pfx_possible and pki_arch.is_subject_key_available(spec.label) + pfx = pki_arch.is_subject_key_available(spec.label) return AnimatorCertInfo( spec=spec, pfx_available=pfx, @@ -111,7 +108,7 @@ class ArchServicesDescription: cert_repo: list attr_cert_repo: list certs_by_issuer: Dict[EntityLabel, List[AnimatorCertInfo]] - attr_certs_by_issuer: Dict[EntityLabel, List[AnimatorCertInfo]] + attr_certs_by_issuer: Dict[EntityLabel, List[AnimatorAttrCertInfo]] @classmethod def compile(cls, pki_arch: PKIArchitecture): @@ -252,7 +249,6 @@ def gen_index(architectures): pki_archs=[ ArchServicesDescription.compile(arch) for arch in architectures ], - pfx_possible=pfx_possible, web_ui_prefix=WEB_UI_URL_PREFIX, ) @@ -543,10 +539,7 @@ def serve_pfx(self, request: Request, *, arch): raise BadRequest() cert_label = CertLabel(cert) - if not ( - pyca_cryptography_present() - and pki_arch.is_subject_key_available(cert_label) - ): + if not pki_arch.is_subject_key_available(cert_label): raise NotFound() pass_bytes = request.form.get('passphrase', '').encode('utf8') diff --git a/certomancer/integrations/animator_templates/arch_summary.html b/certomancer/integrations/animator_templates/arch_summary.html index ee026dc..43a2bfc 100644 --- a/certomancer/integrations/animator_templates/arch_summary.html +++ b/certomancer/integrations/animator_templates/arch_summary.html @@ -41,55 +41,51 @@

Attribute certificates by issuer

{% endfor %}
- {% if pfx_possible %} -

Download PKCS #12 (.pfx) bundles

-

- Choose a certificate label that you want to download - together with its issuance chain and private key. - You can optionally set a passphrase. -

-
- - - - - - - - - - - - - - - - - - -
- - - -
- - - -
- -
-
- {% else %} -

[PKCS #12 support is unavailable.]

- {% endif %} +

Download PKCS #12 (.pfx) bundles

+

+ Choose a certificate label that you want to download + together with its issuance chain and private key. + You can optionally set a passphrase. +

+
+ + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ +
+

Time stamping endpoints (RFC 3161 protocol)

diff --git a/certomancer/registry/pki_arch.py b/certomancer/registry/pki_arch.py index 4559026..bcce103 100644 --- a/certomancer/registry/pki_arch.py +++ b/certomancer/registry/pki_arch.py @@ -18,7 +18,7 @@ check_config_keys, key_dashes_to_underscores, ) -from ..crypto_utils import load_cert_from_pemder, pyca_cryptography_present +from ..crypto_utils import load_cert_from_pemder from ..services import ( CertomancerServiceError, CRLBuilder, @@ -680,7 +680,6 @@ def is_subject_key_available(self, cert: CertLabel): def _dump_certs( self, use_pem=True, flat=False, include_pkcs12=False, pkcs12_pass=None ): - include_pkcs12 &= pyca_cryptography_present() # start writing only after we know that all certs have been built ext = '.cert.pem' if use_pem else '.crt' for iss_label, iss_certs in self._cert_labels_by_issuer.items(): diff --git a/docs/deploy.md b/docs/deploy.md index 2968c6b..0a78662 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -10,7 +10,7 @@ uWSGI is extremely easy if you've dealt with WSGI deployment before. 1. Follow the [uWSGI quickstart](https://uwsgi-docs.readthedocs.io/en/latest/WSGIquickstart.html) guide to learn more about deploying WSGI applications. 2. Install Certomancer using `pip install certomancer[web-api]` (either system-wide or in a - virtualenv). If you need PKCS#12 support, run `pip install certomancer[web-api,pkcs12]` or + virtualenv). install the [pyca/cryptography](https://github.com/pyca/cryptography) library manually. 3. Generate or copy over some testing keys (RSA, DSA, ECDSA and EdDSA are supported). 4. Set `module = certomancer.integrations.animator:app` in your `uwsgi.ini` file. diff --git a/example_plugin/encrypt_echo.py b/example_plugin/encrypt_echo.py index 50ec23c..8ba1084 100644 --- a/example_plugin/encrypt_echo.py +++ b/example_plugin/encrypt_echo.py @@ -11,7 +11,11 @@ from typing import Optional from asn1crypto import cms, algos -from oscrypto import asymmetric, symmetric +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 from certomancer import registry from certomancer.registry import plugin_api @@ -45,8 +49,9 @@ def invoke(self, arch: registry.PKIArchitecture, envelope_key = secrets.token_bytes(32) # encrypt the envelope key with the recipient's public key - key = asymmetric.load_public_key(cert.public_key.dump()) - encrypted_data = asymmetric.rsa_pkcs1v15_encrypt(key, envelope_key) + + key: RSAPublicKey = serialization.load_der_public_key(cert.public_key.dump()) + encrypted_data = key.encrypt(envelope_key, padding.PKCS1v15()) rid = cms.RecipientIdentifier({ 'issuer_and_serial_number': cms.IssuerAndSerialNumber({ @@ -66,9 +71,12 @@ def invoke(self, arch: registry.PKIArchitecture, }) # encrypt the request body - iv, encrypted_envelope_content = symmetric.aes_cbc_pkcs7_encrypt( - envelope_key, request, iv=None - ) + iv = secrets.token_bytes(16) + cipher = Cipher(algorithms.AES(envelope_key), modes.CBC(iv)) + padder = PKCS7(128).padder() + padded = padder.update(request) + padder.finalize() + enc = cipher.encryptor() + encrypted_envelope_content = enc.update(padded) + enc.finalize() algo = cms.EncryptionAlgorithm({ 'algorithm': algos.EncryptionAlgorithmId('aes256_cbc'), diff --git a/pyproject.toml b/pyproject.toml index b7e0231..164c509 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,10 +27,10 @@ classifiers = [ dependencies = [ "asn1crypto>=1.5.0", "click>=7.1.2", - "oscrypto>=1.2.1", "pyyaml>=5.4.1", "python-dateutil>=2.8.1", "tzlocal>=2.1", + "cryptography>=3.4.7" ] [project.readme] @@ -43,7 +43,6 @@ Homepage = "https://github.com/MatthiasValvekens/certomancer" [project.optional-dependencies] requests-mocker = ["requests-mock>=1.8.0"] web-api = ["Werkzeug>=2.2.3", "Jinja2>=2.11.3"] -pkcs12 = ["cryptography>=3.4.7"] pkcs11 = ["python-pkcs11~=0.7.0"] testing-minimal = [ "pytest>=6.1.1", @@ -56,7 +55,7 @@ testing-minimal = [ ] testing = [ "pyhanko-certvalidator==0.23.0", - "certomancer[testing-minimal,pkcs11,pkcs12]" + "certomancer[testing-minimal,pkcs11]" ] mypy = [ "types-requests", @@ -90,7 +89,6 @@ files = 'certomancer' module = [ "asn1crypto.*", "pkcs11.*", - "oscrypto.*", ] ignore_missing_imports = true @@ -102,6 +100,5 @@ log_cli_level = "INFO" testpaths = "tests" asyncio_mode = "strict" markers = [ - "needcrypto: requires pyca/cryptography package", "config_context: require a specific configuration file" ] diff --git a/tests/conftest.py b/tests/conftest.py index 26ecca1..c6584c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,35 +3,9 @@ import re import shutil -import click -import pytest - -from certomancer import crypto_utils - -if crypto_utils.pyca_cryptography_present(): - backends = ["backend:oscrypto", "backend:cryptography"] -else: - backends = ["backend:oscrypto"] - - TEST_DATA_PATH = pathlib.Path("tests", "data").absolute() -@pytest.fixture(scope="session", autouse=True, params=backends) -def crypto_backend(request): - if 'oscrypto' in request.param: - backend = crypto_utils.OscryptoBackend() - else: - backend = crypto_utils.PycaCryptographyBackend() - # the monkeypatch fixture is function-scoped, so let's do it by hand - orig_backend = crypto_utils.CRYPTO_BACKEND - crypto_utils.CRYPTO_BACKEND = backend - try: - yield backend - finally: - crypto_utils.CRYPTO_BACKEND = orig_backend - - def collect_files(path): for cur, dirs, files in os.walk(str(path)): for file in files: diff --git a/tests/test_animator.py b/tests/test_animator.py index f09bc2d..b1825b1 100644 --- a/tests/test_animator.py +++ b/tests/test_animator.py @@ -7,10 +7,12 @@ import pytest import pytz from asn1crypto import algos, cms, core, crl, ocsp, tsp, x509 +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 from freezegun import freeze_time -from oscrypto import asymmetric -from oscrypto import keys as oskeys -from oscrypto import symmetric from werkzeug.test import Client from werkzeug.wrappers import Response @@ -97,23 +99,26 @@ def test_demo_plugin(): # decrypt it env_data = cms.ContentInfo.load(response.data)['content'] arch = with_plugin_cfg.get_pki_arch(ArchLabel('testing-ca')) - key = arch.key_set.get_private_key(KeyLabel('signer1')) + key_info = arch.key_set.get_private_key(KeyLabel('signer1')) ktri = env_data['recipient_infos'][0].chosen encrypted_key = ktri['encrypted_key'].native - decrypted_key = asymmetric.rsa_pkcs1v15_decrypt( - asymmetric.load_private_key(key.dump()), encrypted_key + key: RSAPrivateKey = serialization.load_der_private_key( + key_info.dump(), password=None ) - + decrypted_key = key.decrypt(encrypted_key, padding.PKCS1v15()) eci = env_data['encrypted_content_info'] cea = eci['content_encryption_algorithm'] assert cea['algorithm'].native == 'aes256_cbc' iv = cea['parameters'].native encrypted_content_bytes = eci['encrypted_content'].native - decrypted_payload = symmetric.aes_cbc_pkcs7_decrypt( - decrypted_key, encrypted_content_bytes, iv - ) - assert decrypted_payload == payload + + cipher = Cipher(algorithms.AES(decrypted_key), modes.CBC(iv)) + dec = cipher.decryptor() + decrypted_payload = dec.update(encrypted_content_bytes) + dec.finalize() + unpadder = PKCS7(128).unpadder() + result = unpadder.update(decrypted_payload) + unpadder.finalize() + assert result == payload def test_crl(): @@ -217,14 +222,6 @@ def test_pkcs12(pw): data['passphrase'] = pw.decode('ascii') response = CLIENT.post('/_certomancer/pfx-download/testing-ca', data=data) package = response.data - if pw: - # there's something about passwordless PKCS#12 files that doesn't quite - # jive between oscrypto and pyca/cryptography - key, cert, chain = oskeys.parse_pkcs12(package, password=pw) - assert 'Alice' in cert.subject.human_friendly - assert len(chain) == 2 - assert key is not None - from cryptography.hazmat.primitives.serialization import pkcs12 key, cert, chain = pkcs12.load_key_and_certificates(package, password=pw) diff --git a/tests/test_certs.py b/tests/test_certs.py index 2649e6b..819f9c3 100644 --- a/tests/test_certs.py +++ b/tests/test_certs.py @@ -11,11 +11,10 @@ import pytz import yaml from asn1crypto import cms, core, x509 -from oscrypto import keys as oskeys from certomancer import CertProfilePlugin from certomancer.config_utils import ConfigurationError, SearchDir -from certomancer.crypto_utils import load_cert_from_pemder +from certomancer.crypto_utils import CRYPTO_BACKEND, load_cert_from_pemder from certomancer.registry import ( ArchLabel, CertLabel, @@ -56,7 +55,7 @@ def _keys(): # in the file name if 'pub' in fname: cfg['public-only'] = True - else: + elif not 'aa.key.pem' in fname: cfg['password'] = DUMMY_PASSWORD.decode('ascii') yield m.group(1), cfg @@ -559,7 +558,7 @@ def test_sign_public_only(): ) pubkey = arch.get_cert(CertLabel('leaf')).public_key with open('tests/data/keys-rsa/split-key-pub.key.pem', 'rb') as inf: - pubkey_actual = oskeys.parse_public(inf.read()) + pubkey_actual = CRYPTO_BACKEND.load_public_key(inf.read()) assert pubkey.native == pubkey_actual.native @@ -707,13 +706,6 @@ def test_pss_exclusive(): def test_pkcs12(pw): arch = CONFIG.get_pki_arch(ArchLabel('testing-ca')) package = arch.package_pkcs12(CertLabel('signer1'), password=pw) - if pw: - # there's something about passwordless PKCS#12 files that doesn't quite - # jive between oscrypto and pyca/cryptography - key, cert, chain = oskeys.parse_pkcs12(package, password=pw) - assert cert.dump() == arch.get_cert(CertLabel('signer1')).dump() - assert len(chain) == 2 - assert key is not None from cryptography.hazmat.primitives.serialization import pkcs12 diff --git a/tests/test_services.py b/tests/test_services.py index b206dc6..96e12fb 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -7,10 +7,13 @@ import pytz import requests from asn1crypto import algos, cms, core, ocsp, tsp +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.padding import PKCS7 from freezegun import freeze_time -from oscrypto import asymmetric, symmetric -from certomancer import crypto_utils from certomancer.integrations import illusionist from certomancer.registry import ( ArchLabel, @@ -40,12 +43,7 @@ def _setup(cfgfile) -> ServiceSetup: @pytest.fixture( scope='module', params=['rsa', 'dsa', 'ecdsa', 'ed25519', 'ed448'] ) -def setup(request, crypto_backend): - if isinstance(crypto_backend, crypto_utils.OscryptoBackend): - if request.param not in ('rsa', 'ecdsa'): - pytest.skip( - f'oscrypto backend does not support "{request.param}" setup' - ) +def setup(request): if request.param == 'rsa': return RSA_SETUP else: @@ -310,23 +308,27 @@ def test_demo_plugin(requests_mock): # decrypt it env_data = cms.ContentInfo.load(response.content)['content'] - key = arch.key_set.get_private_key(KeyLabel('signer1')) + key_info = arch.key_set.get_private_key(KeyLabel('signer1')) ktri = env_data['recipient_infos'][0].chosen encrypted_key = ktri['encrypted_key'].native - decrypted_key = asymmetric.rsa_pkcs1v15_decrypt( - asymmetric.load_private_key(key.dump()), encrypted_key + key: RSAPrivateKey = serialization.load_der_private_key( + key_info.dump(), password=None ) + decrypted_key = key.decrypt(encrypted_key, padding.PKCS1v15()) eci = env_data['encrypted_content_info'] cea = eci['content_encryption_algorithm'] assert cea['algorithm'].native == 'aes256_cbc' iv = cea['parameters'].native encrypted_content_bytes = eci['encrypted_content'].native - decrypted_payload = symmetric.aes_cbc_pkcs7_decrypt( - decrypted_key, encrypted_content_bytes, iv - ) - assert decrypted_payload == payload + + cipher = Cipher(algorithms.AES(decrypted_key), modes.CBC(iv)) + dec = cipher.decryptor() + decrypted_payload = dec.update(encrypted_content_bytes) + dec.finalize() + unpadder = PKCS7(128).unpadder() + result = unpadder.update(decrypted_payload) + unpadder.finalize() + assert result == payload def test_svc_template_result():