pax_global_header00006660000000000000000000000064146557170000014517gustar00rootroot0000000000000052 comment=66e7d260e414320f785f10c52e80e986b09d4034 pyopenssl-24.2.1/000077500000000000000000000000001465571700000136415ustar00rootroot00000000000000pyopenssl-24.2.1/.github/000077500000000000000000000000001465571700000152015ustar00rootroot00000000000000pyopenssl-24.2.1/.github/dependabot.yml000066400000000000000000000001651465571700000200330ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" pyopenssl-24.2.1/.github/workflows/000077500000000000000000000000001465571700000172365ustar00rootroot00000000000000pyopenssl-24.2.1/.github/workflows/ci.yml000066400000000000000000000100071465571700000203520ustar00rootroot00000000000000name: CI on: pull_request: {} push: {} jobs: linux: runs-on: ${{ matrix.PYTHON.OS || 'ubuntu-22.04' }} strategy: matrix: PYTHON: # Base builds - {VERSION: "3.7", TOXENV: "py37"} - {VERSION: "3.8", TOXENV: "py38"} - {VERSION: "3.9", TOXENV: "py39"} - {VERSION: "3.10", TOXENV: "py310"} - {VERSION: "3.11", TOXENV: "py311"} - {VERSION: "3.12", TOXENV: "py312"} - {VERSION: "pypy-3.8", TOXENV: "pypy3"} - {VERSION: "pypy-3.9", TOXENV: "pypy3"} - {VERSION: "3.11", TOXENV: "py311-useWheel", OS: "windows-2022" } # -cryptographyMain - {VERSION: "3.7", TOXENV: "py37-cryptographyMain"} - {VERSION: "3.8", TOXENV: "py38-cryptographyMain"} - {VERSION: "3.9", TOXENV: "py39-cryptographyMain"} - {VERSION: "3.10", TOXENV: "py310-cryptographyMain"} - {VERSION: "3.11", TOXENV: "py311-cryptographyMain"} - {VERSION: "3.12", TOXENV: "py312-cryptographyMain"} - {VERSION: "pypy-3.8", TOXENV: "pypy3-cryptographyMain"} - {VERSION: "pypy-3.9", TOXENV: "pypy3-cryptographyMain"} # -cryptographyMinimum - {VERSION: "3.7", TOXENV: "py37-cryptographyMinimum"} - {VERSION: "3.8", TOXENV: "py38-cryptographyMinimum"} - {VERSION: "3.9", TOXENV: "py39-cryptographyMinimum"} - {VERSION: "3.10", TOXENV: "py310-cryptographyMinimum"} - {VERSION: "3.11", TOXENV: "py311-cryptographyMinimum"} - {VERSION: "3.12", TOXENV: "py312-cryptographyMinimum"} - {VERSION: "pypy-3.8", TOXENV: "pypy3-cryptographyMinimum"} # Cryptography wheels - {VERSION: "3.9", TOXENV: "py39-cryptographyMinimum-useWheel"} - {VERSION: "3.9", TOXENV: "py39-useWheel"} # Random order - {VERSION: "3.9", TOXENV: "py39-randomorder"} # Downstreams - {VERSION: "3.11", TOXENV: "py311-twistedTrunk"} # Meta - {VERSION: "3.9", TOXENV: "check-manifest"} - {VERSION: "3.11", TOXENV: "lint"} - {VERSION: "3.11", TOXENV: "py311-mypy"} - {VERSION: "3.9", TOXENV: "docs"} name: "${{ matrix.PYTHON.TOXENV }}${{ matrix.PYTHON.OS && format(' on {0}', matrix.PYTHON.OS) || '' }}" steps: - uses: actions/checkout@v4 - name: Setup python uses: actions/setup-python@v5 with: python-version: ${{ matrix.PYTHON.VERSION }} - run: python -m pip install tox coverage - run: tox -v env: TOXENV: ${{ matrix.PYTHON.TOXENV }} - name: Upload coverage run: | curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash bash codecov.sh -n "tox -e ${{ matrix.PYTHON.TOXENV }}" linux-docker: runs-on: ubuntu-latest container: ghcr.io/pyca/cryptography-runner-${{ matrix.TEST.CONTAINER }} strategy: matrix: TEST: # cryptographyMain used since there's no wheel - {CONTAINER: "ubuntu-rolling", TOXENV: "py312-cryptographyMain"} name: "${{ matrix.TEST.TOXENV }} on ${{ matrix.TEST.CONTAINER }}" steps: - uses: actions/checkout@v4 - run: /venv/bin/pip install tox - run: /venv/bin/tox -v env: TOXENV: ${{ matrix.TEST.TOXENV }} RUSTUP_HOME: /root/.rustup - name: Upload coverage run: | curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash || curl -o codecov.sh -f https://codecov.io/bash bash codecov.sh -n "tox -e ${{ matrix.TEST.TOXENV }} on ${{ matrix.TEST.CONTAINER }}" all-green: runs-on: ubuntu-latest needs: [linux, linux-docker] if: ${{ always() }} timeout-minutes: 3 steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} pyopenssl-24.2.1/.github/workflows/lock.yml000066400000000000000000000004701465571700000207120ustar00rootroot00000000000000name: Lock Issues on: schedule: - cron: '0 0 * * *' permissions: issues: "write" jobs: lock: runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v5 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: 90 pr-inactive-days: 90 pyopenssl-24.2.1/.gitignore000066400000000000000000000002201465571700000156230ustar00rootroot00000000000000build dist *.egg-info *.pyc *.pyo __pycache__ .tox doc/_build/ .coverage* .eggs examples/simple/*.cert examples/simple/*.pkey .cache .mypy_cachepyopenssl-24.2.1/.readthedocs.yml000066400000000000000000000001671465571700000167330ustar00rootroot00000000000000version: 2 build: os: "ubuntu-22.04" tools: python: "3" jobs: post_install: - pip install .[docs] pyopenssl-24.2.1/CHANGELOG.rst000066400000000000000000000544351465571700000156750ustar00rootroot00000000000000Changelog ========= Versions are year-based with a strict backward-compatibility policy. The third digit is only for regressions. 24.2.1 (2024-07-20) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Fixed changelog to remove sphinx specific restructured text strings. 24.2.0 (2024-07-20) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.crypto.X509Req``, ``OpenSSL.crypto.load_certificate_request``, ``OpenSSL.crypto.dump_certificate_request``. Instead, ``cryptography.x509.CertificateSigningRequest``, ``cryptography.x509.CertificateSigningRequestBuilder``, ``cryptography.x509.load_der_x509_csr``, or ``cryptography.x509.load_pem_x509_csr`` should be used. Changes: ^^^^^^^^ - Added type hints for the ``SSL`` module. `#1308 `_. - Changed ``OpenSSL.crypto.PKey.from_cryptography_key`` to accept public and private EC, ED25519, ED448 keys. `#1310 `_. 24.1.0 (2024-03-09) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ * Removed the deprecated ``OpenSSL.crypto.PKCS12`` and ``OpenSSL.crypto.NetscapeSPKI``. ``OpenSSL.crypto.PKCS12`` may be replaced by the PKCS#12 APIs in the ``cryptography`` package. Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ 24.0.0 (2024-01-22) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Added ``OpenSSL.SSL.Connection.get_selected_srtp_profile`` to determine which SRTP profile was negotiated. `#1279 `_. 23.3.0 (2023-10-25) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Dropped support for Python 3.6. - The minimum ``cryptography`` version is now 41.0.5. - Removed ``OpenSSL.crypto.load_pkcs7`` and ``OpenSSL.crypto.load_pkcs12`` which had been deprecated for 3 years. - Added ``OpenSSL.SSL.OP_LEGACY_SERVER_CONNECT`` to allow legacy insecure renegotiation between OpenSSL and unpatched servers. `#1234 `_. Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.crypto.PKCS12`` (which was intended to have been deprecated at the same time as ``OpenSSL.crypto.load_pkcs12``). - Deprecated ``OpenSSL.crypto.NetscapeSPKI``. - Deprecated ``OpenSSL.crypto.CRL`` - Deprecated ``OpenSSL.crypto.Revoked`` - Deprecated ``OpenSSL.crypto.load_crl`` and ``OpenSSL.crypto.dump_crl`` - Deprecated ``OpenSSL.crypto.sign`` and ``OpenSSL.crypto.verify`` - Deprecated ``OpenSSL.crypto.X509Extension`` Changes: ^^^^^^^^ - Changed ``OpenSSL.crypto.X509Store.add_crl`` to also accept ``cryptography``'s ``x509.CertificateRevocationList`` arguments in addition to the now deprecated ``OpenSSL.crypto.CRL`` arguments. - Fixed ``test_set_default_verify_paths`` test so that it is skipped if no network connection is available. 23.2.0 (2023-05-30) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Removed ``X509StoreFlags.NOTIFY_POLICY``. `#1213 `_. Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - ``cryptography`` maximum version has been increased to 41.0.x. - Invalid versions are now rejected in ``OpenSSL.crypto.X509Req.set_version``. - Added ``X509VerificationCodes`` to ``OpenSSL.SSL``. `#1202 `_. 23.1.1 (2023-03-28) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Worked around an issue in OpenSSL 3.1.0 which caused `X509Extension.get_short_name` to raise an exception when no short name was known to OpenSSL. `#1204 `_. 23.1.0 (2023-03-24) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - ``cryptography`` maximum version has been increased to 40.0.x. - Add ``OpenSSL.SSL.Connection.DTLSv1_get_timeout`` and ``OpenSSL.SSL.Connection.DTLSv1_handle_timeout`` to support DTLS timeouts `#1180 `_. 23.0.0 (2023-01-01) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Add ``OpenSSL.SSL.X509StoreFlags.PARTIAL_CHAIN`` constant to allow for users to perform certificate verification on partial certificate chains. `#1166 `_ - ``cryptography`` maximum version has been increased to 39.0.x. 22.1.0 (2022-09-25) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Remove support for SSLv2 and SSLv3. - The minimum ``cryptography`` version is now 38.0.x (and we now pin releases against ``cryptography`` major versions to prevent future breakage) - The ``OpenSSL.crypto.X509StoreContextError`` exception has been refactored, changing its internal attributes. `#1133 `_ Deprecations: ^^^^^^^^^^^^^ - ``OpenSSL.SSL.SSLeay_version`` is deprecated in favor of ``OpenSSL.SSL.OpenSSL_version``. The constants ``OpenSSL.SSL.SSLEAY_*`` are deprecated in favor of ``OpenSSL.SSL.OPENSSL_*``. Changes: ^^^^^^^^ - Add ``OpenSSL.SSL.Connection.set_verify`` and ``OpenSSL.SSL.Connection.get_verify_mode`` to override the context object's verification flags. `#1073 `_ - Add ``OpenSSL.SSL.Connection.use_certificate`` and ``OpenSSL.SSL.Connection.use_privatekey`` to set a certificate per connection (and not just per context) `#1121 `_. 22.0.0 (2022-01-29) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Drop support for Python 2.7. `#1047 `_ - The minimum ``cryptography`` version is now 35.0. Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Expose wrappers for some `DTLS `_ primitives. `#1026 `_ 21.0.0 (2021-09-28) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The minimum ``cryptography`` version is now 3.3. - Drop support for Python 3.5 Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Raise an error when an invalid ALPN value is set. `#993 `_ - Added ``OpenSSL.SSL.Context.set_min_proto_version`` and ``OpenSSL.SSL.Context.set_max_proto_version`` to set the minimum and maximum supported TLS version `#985 `_. - Updated ``to_cryptography`` and ``from_cryptography`` methods to support an upcoming release of ``cryptography`` without raising deprecation warnings. `#1030 `_ 20.0.1 (2020-12-15) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Deprecations: ^^^^^^^^^^^^^ Changes: ^^^^^^^^ - Fixed compatibility with OpenSSL 1.1.0. 20.0.0 (2020-11-27) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The minimum ``cryptography`` version is now 3.2. - Remove deprecated ``OpenSSL.tsafe`` module. - Removed deprecated ``OpenSSL.SSL.Context.set_npn_advertise_callback``, ``OpenSSL.SSL.Context.set_npn_select_callback``, and ``OpenSSL.SSL.Connection.get_next_proto_negotiated``. - Drop support for Python 3.4 - Drop support for OpenSSL 1.0.1 and 1.0.2 Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.crypto.load_pkcs7`` and ``OpenSSL.crypto.load_pkcs12``. Changes: ^^^^^^^^ - Added a new optional ``chain`` parameter to ``OpenSSL.crypto.X509StoreContext()`` where additional untrusted certificates can be specified to help chain building. `#948 `_ - Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted certificate file bundles and/or directories for verification. `#943 `_ - Added ``Context.set_keylog_callback`` to log key material. `#910 `_ - Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the verified certificate chain of the peer. `#894 `_. - Make verification callback optional in ``Context.set_verify``. If omitted, OpenSSL's default verification is used. `#933 `_ - Fixed a bug that could truncate or cause a zero-length key error due to a null byte in private key passphrase in ``OpenSSL.crypto.load_privatekey`` and ``OpenSSL.crypto.dump_privatekey``. `#947 `_ 19.1.0 (2019-11-18) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Removed deprecated ``ContextType``, ``ConnectionType``, ``PKeyType``, ``X509NameType``, ``X509ReqType``, ``X509Type``, ``X509StoreType``, ``CRLType``, ``PKCS7Type``, ``PKCS12Type``, and ``NetscapeSPKIType`` aliases. Use the classes without the ``Type`` suffix instead. `#814 `_ - The minimum ``cryptography`` version is now 2.8 due to issues on macOS with a transitive dependency. `#875 `_ Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.SSL.Context.set_npn_advertise_callback``, ``OpenSSL.SSL.Context.set_npn_select_callback``, and ``OpenSSL.SSL.Connection.get_next_proto_negotiated``. ALPN should be used instead. `#820 `_ Changes: ^^^^^^^^ - Support ``bytearray`` in ``SSL.Connection.send()`` by using cffi's from_buffer. `#852 `_ - The ``OpenSSL.SSL.Context.set_alpn_select_callback`` can return a new ``NO_OVERLAPPING_PROTOCOLS`` sentinel value to allow a TLS handshake to complete without an application protocol. ---- 19.0.0 (2019-01-21) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ``X509Store.add_cert`` no longer raises an error if you add a duplicate cert. `#787 `_ Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - pyOpenSSL now works with OpenSSL 1.1.1. `#805 `_ - pyOpenSSL now handles NUL bytes in ``X509Name.get_components()`` `#804 `_ ---- 18.0.0 (2018-05-16) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The minimum ``cryptography`` version is now 2.2.1. - Support for Python 2.6 has been dropped. Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Added ``Connection.get_certificate`` to retrieve the local certificate. `#733 `_ - ``OpenSSL.SSL.Connection`` now sets ``SSL_MODE_AUTO_RETRY`` by default. `#753 `_ - Added ``Context.set_tlsext_use_srtp`` to enable negotiation of SRTP keying material. `#734 `_ ---- 17.5.0 (2017-11-30) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - The minimum ``cryptography`` version is now 2.1.4. Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Fixed a potential use-after-free in the verify callback and resolved a memory leak when loading PKCS12 files with ``cacerts``. `#723 `_ - Added ``Connection.export_keying_material`` for RFC 5705 compatible export of keying material. `#725 `_ ---- 17.4.0 (2017-11-21) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Re-added a subset of the ``OpenSSL.rand`` module. This subset allows conscientious users to reseed the OpenSSL CSPRNG after fork. `#708 `_ - Corrected a use-after-free when reusing an issuer or subject from an ``X509`` object after the underlying object has been mutated. `#709 `_ ---- 17.3.0 (2017-09-14) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Dropped support for Python 3.3. `#677 `_ - Removed the deprecated ``OpenSSL.rand`` module. This is being done ahead of our normal deprecation schedule due to its lack of use and the fact that it was becoming a maintenance burden. ``os.urandom()`` should be used instead. `#675 `_ Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.tsafe``. `#673 `_ Changes: ^^^^^^^^ - Fixed a memory leak in ``OpenSSL.crypto.CRL``. `#690 `_ - Fixed a memory leak when verifying certificates with ``OpenSSL.crypto.X509StoreContext``. `#691 `_ ---- 17.2.0 (2017-07-20) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ - Deprecated ``OpenSSL.rand`` - callers should use ``os.urandom()`` instead. `#658 `_ Changes: ^^^^^^^^ - Fixed a bug causing ``Context.set_default_verify_paths()`` to not work with cryptography ``manylinux1`` wheels on Python 3.x. `#665 `_ - Fixed a crash with (EC)DSA signatures in some cases. `#670 `_ ---- 17.1.0 (2017-06-30) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Removed the deprecated ``OpenSSL.rand.egd()`` function. Applications should prefer ``os.urandom()`` for random number generation. `#630 `_ - Removed the deprecated default ``digest`` argument to ``OpenSSL.crypto.CRL.export()``. Callers must now always pass an explicit ``digest``. `#652 `_ - Fixed a bug with ``ASN1_TIME`` casting in ``X509.set_notBefore()``, ``X509.set_notAfter()``, ``Revoked.set_rev_date()``, ``Revoked.set_nextUpdate()``, and ``Revoked.set_lastUpdate()``. You must now pass times in the form ``YYYYMMDDhhmmssZ``. ``YYYYMMDDhhmmss+hhmm`` and ``YYYYMMDDhhmmss-hhmm`` will no longer work. `#612 `_ Deprecations: ^^^^^^^^^^^^^ - Deprecated the legacy "Type" aliases: ``ContextType``, ``ConnectionType``, ``PKeyType``, ``X509NameType``, ``X509ExtensionType``, ``X509ReqType``, ``X509Type``, ``X509StoreType``, ``CRLType``, ``PKCS7Type``, ``PKCS12Type``, ``NetscapeSPKIType``. The names without the "Type"-suffix should be used instead. Changes: ^^^^^^^^ - Added ``OpenSSL.crypto.X509.from_cryptography()`` and ``OpenSSL.crypto.X509.to_cryptography()`` for converting X.509 certificate to and from pyca/cryptography objects. `#640 `_ - Added ``OpenSSL.crypto.X509Req.from_cryptography()``, ``OpenSSL.crypto.X509Req.to_cryptography()``, ``OpenSSL.crypto.CRL.from_cryptography()``, and ``OpenSSL.crypto.CRL.to_cryptography()`` for converting X.509 CSRs and CRLs to and from pyca/cryptography objects. `#645 `_ - Added ``OpenSSL.debug`` that allows to get an overview of used library versions (including linked OpenSSL) and other useful runtime information using ``python -m OpenSSL.debug``. `#620 `_ - Added a fallback path to ``Context.set_default_verify_paths()`` to accommodate the upcoming release of ``cryptography`` ``manylinux1`` wheels. `#633 `_ ---- 17.0.0 (2017-04-20) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Added ``OpenSSL.X509Store.set_time()`` to set a custom verification time when verifying certificate chains. `#567 `_ - Added a collection of functions for working with OCSP stapling. None of these functions make it possible to validate OCSP assertions, only to staple them into the handshake and to retrieve the stapled assertion if provided. Users will need to write their own code to handle OCSP assertions. We specifically added: ``Context.set_ocsp_server_callback()``, ``Context.set_ocsp_client_callback()``, and ``Connection.request_ocsp()``. `#580 `_ - Changed the ``SSL`` module's memory allocation policy to avoid zeroing memory it allocates when unnecessary. This reduces CPU usage and memory allocation time by an amount proportional to the size of the allocation. For applications that process a lot of TLS data or that use very lage allocations this can provide considerable performance improvements. `#578 `_ - Automatically set ``SSL_CTX_set_ecdh_auto()`` on ``OpenSSL.SSL.Context``. `#575 `_ - Fix empty exceptions from ``OpenSSL.crypto.load_privatekey()``. `#581 `_ ---- 16.2.0 (2016-10-15) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Fixed compatibility errors with OpenSSL 1.1.0. - Fixed an issue that caused failures with subinterpreters and embedded Pythons. `#552 `_ ---- 16.1.0 (2016-08-26) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ - Dropped support for OpenSSL 0.9.8. Changes: ^^^^^^^^ - Fix memory leak in ``OpenSSL.crypto.dump_privatekey()`` with ``FILETYPE_TEXT``. `#496 `_ - Enable use of CRL (and more) in verify context. `#483 `_ - ``OpenSSL.crypto.PKey`` can now be constructed from ``cryptography`` objects and also exported as such. `#439 `_ - Support newer versions of ``cryptography`` which use opaque structs for OpenSSL 1.1.0 compatibility. ---- 16.0.0 (2016-03-19) ------------------- This is the first release under full stewardship of PyCA. We have made *many* changes to make local development more pleasing. The test suite now passes both on Linux and OS X with OpenSSL 0.9.8, 1.0.1, and 1.0.2. It has been moved to `pytest `_, all CI test runs are part of `tox `_ and the source code has been made fully `flake8 `_ compliant. We hope to have lowered the barrier for contributions significantly but are open to hear about any remaining frustrations. Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Python 3.2 support has been dropped. It never had significant real world usage and has been dropped by our main dependency ``cryptography``. Affected users should upgrade to Python 3.3 or later. Deprecations: ^^^^^^^^^^^^^ - The support for EGD has been removed. The only affected function ``OpenSSL.rand.egd()`` now uses ``os.urandom()`` to seed the internal PRNG instead. Please see `pyca/cryptography#1636 `_ for more background information on this decision. In accordance with our backward compatibility policy ``OpenSSL.rand.egd()`` will be *removed* no sooner than a year from the release of 16.0.0. Please note that you should `use urandom `_ for all your secure random number needs. - Python 2.6 support has been deprecated. Our main dependency ``cryptography`` deprecated 2.6 in version 0.9 (2015-05-14) with no time table for actually dropping it. pyOpenSSL will drop Python 2.6 support once ``cryptography`` does. Changes: ^^^^^^^^ - Fixed ``OpenSSL.SSL.Context.set_session_id``, ``OpenSSL.SSL.Connection.renegotiate``, ``OpenSSL.SSL.Connection.renegotiate_pending``, and ``OpenSSL.SSL.Context.load_client_ca``. They were lacking an implementation since 0.14. `#422 `_ - Fixed segmentation fault when using keys larger than 4096-bit to sign data. `#428 `_ - Fixed ``AttributeError`` when ``OpenSSL.SSL.Connection.get_app_data()`` was called before setting any app data. `#304 `_ - Added ``OpenSSL.crypto.dump_publickey()`` to dump ``OpenSSL.crypto.PKey`` objects that represent public keys, and ``OpenSSL.crypto.load_publickey()`` to load such objects from serialized representations. `#382 `_ - Added ``OpenSSL.crypto.dump_crl()`` to dump a certificate revocation list out to a string buffer. `#368 `_ - Added ``OpenSSL.SSL.Connection.get_state_string()`` using the OpenSSL binding ``state_string_long``. `#358 `_ - Added support for the ``socket.MSG_PEEK`` flag to ``OpenSSL.SSL.Connection.recv()`` and ``OpenSSL.SSL.Connection.recv_into()``. `#294 `_ - Added ``OpenSSL.SSL.Connection.get_protocol_version()`` and ``OpenSSL.SSL.Connection.get_protocol_version_name()``. `#244 `_ - Switched to ``utf8string`` mask by default. OpenSSL formerly defaulted to a ``T61String`` if there were UTF-8 characters present. This was changed to default to ``UTF8String`` in the config around 2005, but the actual code didn't change it until late last year. This will default us to the setting that actually works. To revert this you can call ``OpenSSL.crypto._lib.ASN1_STRING_set_default_mask_asc(b"default")``. `#234 `_ ---- Older Changelog Entries ----------------------- The changes from before release 16.0.0 are preserved in the `repository `_. pyopenssl-24.2.1/CODE_OF_CONDUCT.rst000066400000000000000000000064111465571700000166520ustar00rootroot00000000000000Contributor Covenant Code of Conduct ==================================== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting either the project maintainer Hynek Schlawack at hs@ox.cx or -- e.g. in case of a conflict of interest -- Amber Brown at hawkowl@atleastfornow.net. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `_, version 1.4, available at http://contributor-covenant.org/version/1/4. pyopenssl-24.2.1/CONTRIBUTING.rst000066400000000000000000000122411465571700000163020ustar00rootroot00000000000000Contributing ============ First of all, thank you for your interest in contributing to pyOpenSSL! This project has no company backing its development therefore we're dependent on help by the community. Filing bug reports ------------------ Bug reports are very welcome. Please file them on the `GitHub issue tracker`_. Good bug reports come with extensive descriptions of the error and how to reproduce it. Reporters are strongly encouraged to include an `short, self contained, correct example `_. Patches ------- All patches to pyOpenSSL should be submitted in the form of pull requests to the main pyOpenSSL repository, `pyca/pyopenssl`_. These pull requests should satisfy the following properties: Code ^^^^ - The pull request should focus on one particular improvement to pyOpenSSL. Create different pull requests for unrelated features or bugfixes. - Code should follow `PEP 8`_, especially in the "do what code around you does" sense. Follow OpenSSL naming for callables whenever possible is preferred. - Pull requests that introduce code must test all new behavior they introduce as well as for previously untested or poorly tested behavior that they touch. - Pull requests are not allowed to break existing tests. We usually don't comment on pull requests that are breaking the CI because we consider them work in progress. Please note that not having 100% code coverage for the code you wrote/touched also causes our CI to fail. Documentation ^^^^^^^^^^^^^ When introducing new functionality, please remember to write documentation. - New functions and methods should have a docstring describing what they do, what parameters they takes, and what they return. They should also come with `type hints`_. .. code-block:: python def dump_publickey(type: int, pkey: PKey) -> bytes: """ Dump a public key to a buffer. :param type: The file type (one of :data:`FILETYPE_PEM` or :data:`FILETYPE_ASN1`). :param pkey: The PKey to dump. :return: The buffer with the dumped key in it. """ Don't forget to add an ``.. auto(function|class|method)::`` statement to the relevant API document found in ``doc/api/`` to actually add your function to the Sphinx documentation. - Do *not* use ``:py:`` prefixes when cross-linking (Python is default). Do *not* use the generic ``:data:`` or ``:obj:``. Instead use more specific types like ``:class:``, ``:func:`` or ``:meth:`` if applicable. - Pull requests that introduce features or fix bugs should note those changes in the CHANGELOG.rst_ file. Please add new entries to the *top* of the *current* Changes section followed by a line linking to the relevant pull request: .. code-block:: rst - Added ``OpenSSL.crypto.some_func()`` to do something awesome. [`#1 `_] - Use `semantic newlines`_ in reStructuredText_ files (files ending in ``.rst``). Review ------ Finally, pull requests must be reviewed before merging. This process mirrors the `cryptography code review process`_. Everyone can perform reviews; this is a very valuable way to contribute, and is highly encouraged. Pull requests are merged by `members of PyCA`_. They should, of course, keep all the requirements detailed in this document as well as the ``pyca/cryptography`` merge requirements in mind. The final responsibility for the reviewing of merged code lies with the person merging it. Since pyOpenSSL is a sensitive project from a security perspective, reviewers are strongly encouraged to take this review and merge process very seriously. Finding Help ------------ If you need any help with the contribution process, you'll find us hanging out at ``#cryptography-dev`` on Freenode_ IRC. You can also ask questions on our `mailing list`_. Please note that this project is released with a Contributor `Code of Conduct`_. By participating in this project you agree to abide by its terms. Security -------- If you feel that you found a security-relevant bug that you would prefer to discuss in private, please send us a GPG_-encrypted e-mail. The maintainer can be reached at hs@ox.cx and his GPG key ID is ``0xAE2536227F69F181`` (Fingerprint: ``C2A0 4F86 ACE2 8ADC F817 DBB7 AE25 3622 7F69 F181``). Feel free to cross-check this information with Keybase_. .. _GitHub issue tracker: https://github.com/pyca/pyopenssl/issues .. _GPG: https://en.wikipedia.org/wiki/GNU_Privacy_Guard .. _Keybase: https://keybase.io/hynek .. _pyca/pyopenssl: https://github.com/pyca/pyopenssl .. _PEP 8: https://www.python.org/dev/peps/pep-0008/ .. _`type hints`: https://docs.python.org/3/library/typing.html .. _cryptography code review process: https://cryptography.io/en/latest/development/reviewing-patches/ .. _freenode: https://freenode.net .. _mailing list: https://mail.python.org/mailman/listinfo/cryptography-dev .. _members of PyCA: https://github.com/orgs/pyca/people .. _semantic newlines: http://rhodesmill.org/brandon/2012/one-sentence-per-line/ .. _reStructuredText: http://sphinx-doc.org/rest.html .. _CHANGELOG.rst: https://github.com/pyca/pyopenssl/blob/main/CHANGELOG.rst .. _`Code of Conduct`: https://github.com/pyca/pyopenssl/blob/main/CODE_OF_CONDUCT.rst pyopenssl-24.2.1/INSTALL.rst000066400000000000000000000022551465571700000155050ustar00rootroot00000000000000Installation ============ To install pyOpenSSL:: $ pip install pyopenssl If you are installing in order to *develop* on pyOpenSSL, move to the root directory of a pyOpenSSL checkout, and run:: $ pip install -e .[test] .. warning:: As of 0.14, pyOpenSSL is a pure-Python project. That means that if you encounter *any* kind of compiler errors, pyOpenSSL's bugtracker is the **wrong** place to report them because we *cannot* help you. Please take the time to read the errors and report them/ask help from the appropriate project. The most likely culprit being `cryptography `_ that contains OpenSSL's library bindings. Supported OpenSSL Versions -------------------------- pyOpenSSL supports the same platforms and releases as the upstream cryptography project `does `_. Currently that means: - 1.1.0 - 1.1.1 - 3.0 You can always find out the versions of pyOpenSSL, cryptography, and the linked OpenSSL by running ``python -m OpenSSL.debug``. Documentation ------------- The documentation is written in reStructuredText and built using Sphinx:: $ cd doc $ make html pyopenssl-24.2.1/LICENSE000066400000000000000000000261361465571700000146560ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. pyopenssl-24.2.1/MANIFEST.in000066400000000000000000000003771465571700000154060ustar00rootroot00000000000000include LICENSE MANIFEST.in *.rst tox.ini .coveragerc src/OpenSSL/py.typed exclude codecov.yml .readthedocs.yml mypy.ini recursive-include tests *.py recursive-include doc * prune doc/_build pyopenssl-24.2.1/README.rst000066400000000000000000000034361465571700000153360ustar00rootroot00000000000000======================================================== pyOpenSSL -- A Python wrapper around the OpenSSL library ======================================================== .. image:: https://readthedocs.org/projects/pyopenssl/badge/?version=stable :target: https://pyopenssl.org/en/stable/ :alt: Stable Docs .. image:: https://github.com/pyca/pyopenssl/workflows/CI/badge.svg?branch=main :target: https://github.com/pyca/pyopenssl/actions?query=workflow%3ACI+branch%3Amain .. image:: https://codecov.io/github/pyca/pyopenssl/branch/main/graph/badge.svg :target: https://codecov.io/github/pyca/pyopenssl :alt: Test coverage **Note:** The Python Cryptographic Authority **strongly suggests** the use of `pyca/cryptography`_ where possible. If you are using pyOpenSSL for anything other than making a TLS connection **you should move to cryptography and drop your pyOpenSSL dependency**. High-level wrapper around a subset of the OpenSSL library. Includes * ``SSL.Connection`` objects, wrapping the methods of Python's portable sockets * Callbacks written in Python * Extensive error-handling mechanism, mirroring OpenSSL's error codes ... and much more. You can find more information in the documentation_. Development takes place on GitHub_. Discussion ========== If you run into bugs, you can file them in our `issue tracker`_. We maintain a cryptography-dev_ mailing list for both user and development discussions. You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get involved. .. _documentation: https://pyopenssl.org/ .. _`issue tracker`: https://github.com/pyca/pyopenssl/issues .. _cryptography-dev: https://mail.python.org/mailman/listinfo/cryptography-dev .. _GitHub: https://github.com/pyca/pyopenssl .. _`pyca/cryptography`: https://github.com/pyca/cryptography pyopenssl-24.2.1/codecov.yml000066400000000000000000000000171465571700000160040ustar00rootroot00000000000000comment: false pyopenssl-24.2.1/doc/000077500000000000000000000000001465571700000144065ustar00rootroot00000000000000pyopenssl-24.2.1/doc/ChangeLog_old.txt000066400000000000000000000745121465571700000176450ustar00rootroot00000000000000This file only contains the changes up to release 0.15.1. Newer changes can be found at . *** 2015-04-14 Hynek Schlawack * Release 0.15.1 2015-04-14 Glyph Lefkowitz * OpenSSL/SSL.py, OpenSSL/test/test_ssl.py: Fix a regression present in 0.15, where when an error occurs and no errno() is set, a KeyError is raised. This happens, for example, if Connection.shutdown() is called when the underlying transport has gone away. 2015-04-14 Hynek Schlawack * Release 0.15 2015-04-12 Jean-Paul Calderone * OpenSSL/rand.py, OpenSSL/SSL.py: APIs which previously accepted filenames only as bytes now accept them as either bytes or unicode (and respect sys.getfilesystemencoding()). 2015-03-23 Jean-Paul Calderone * OpenSSL/SSL.py: Add Cory Benfield's next-protocol-negotiation (NPN) bindings. 2015-03-15 Jean-Paul Calderone * OpenSSL/SSL.py: Add ``Connection.recv_into``, mirroring the builtin ``socket.recv_into``. Based on work from Cory Benfield. * OpenSSL/test/test_ssl.py: Add tests for ``recv_into``. 2015-01-30 Stephen Holsapple * OpenSSL/crypto.py: Expose ``X509StoreContext`` for verifying certificates. * OpenSSL/test/test_crypto.py: Add intermediate certificates for 2015-01-08 Paul Aurich * OpenSSL/SSL.py: ``Connection.shutdown`` now propagates errors from the underlying socket. 2014-12-11 Jean-Paul Calderone * OpenSSL/SSL.py: Fixed a regression ``Context.check_privatekey`` causing it to always succeed - even if it should fail. 2014-08-21 Alex Gaynor * OpenSSL/crypto.py: Fixed a regression where calling ``load_pkcs7_data`` with ``FILETYPE_ASN1`` would fail with a ``NameError``. 2014-05-05 Jean-Paul Calderone * OpenSSL/SSL.py: Fix a regression in which the first argument of the "verify" callback was incorrectly passed a ``Context`` instance instead of the ``Connection`` instance. * OpenSSL/test/test_ssl.py: Add a test for the value passed as the first argument of the "verify" callback. 2014-04-19 Jean-Paul Calderone * OpenSSL/crypto.py: Based on work from Alex Gaynor, Andrew Lutomirski, Tobias Oberstein, Laurens Van Houtven, and Hynek Schlawack, add ``get_elliptic_curve`` and ``get_elliptic_curves`` to support TLS ECDHE modes. * OpenSSL/SSL.py: Add ``Context.set_tmp_ecdh`` to configure a TLS context with a particular elliptic curve for ECDHE modes. 2014-04-19 Markus Unterwaditzer * OpenSSL/SSL.py: ``Connection.send`` and ``Connection.sendall`` now also accept the ``buffer`` type as data. 2014-04-05 Stephen Holsapple * OpenSSL/crypto.py: Make ``load_pkcs12`` backwards compatible with pyOpenSSL 0.13 by making passphrase optional. 2014-03-30 Fedor Brunner * OpenSSL/SSL.py: Add ``get_finished``, ``get_peer_finished`` methods to ``Connection``. If you use these methods to implement TLS channel binding (RFC 5929) disable session resumption because triple handshake attacks against TLS. 2014-03-29 Fedor Brunner * OpenSSL/SSL.py: Add ``get_cipher_name``, ``get_cipher_bits``, and ``get_cipher_version`` to ``Connection``. 2014-03-28 Jean-Paul Calderone * OpenSSL/tsafe.py: Replace the use of ``apply`` (which has been removed in Python 3) with the equivalent syntax. 2014-03-28 Jonathan Giannuzzi * OpenSSL/crypto.py: Fix memory leak in _X509_REVOKED_dup. * leakcheck/crypto.py: Add checks for _X509_REVOKED_dup, CRL.add_revoked and CRL.get_revoked. * setup.py: Require cryptography 0.3 to have the ASN1_TIME_free binding. 2014-03-02 Stephen Holsapple * OpenSSL/crypto.py: Add ``get_extensions`` method to ``X509Req``. 2014-02-23 Jean-Paul Calderone * Release 0.14 2014-01-09 Jean-Paul Calderone * OpenSSL: Port to the cffi-based OpenSSL bindings provided by 2013-10-06 Jean-Paul Calderone * OpenSSL/ssl/context.c: Add support for negotiating TLS v1.1 or v1.2. 2013-10-03 Christian Heimes * OpenSSL/crypto/x509.c: Fix an inconsistency in memory management in X509.get_serial_number which leads to crashes on some runtimes (certain Windows/Python 3.3 environments, at least). 2013-08-11 Christian Heimes * OpenSSL/crypto/x509ext.c: Fix handling of NULL bytes inside subjectAltName general names when formatting an X509 extension as a string. * OpenSSL/crypto/x509.c: Fix memory leak in get_extension(). 2012-04-03 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Release the GIL around RSA and DSA key generation, based on code from INADA Naoki. 2012-02-13 Jean-Paul Calderone * OpenSSL/ssl/ssl.c: Add session cache related constants for use with the new Context.set_session_cache_mode method. * OpenSSL/ssl/context.c: Add new Context methods set_session_cache_mode and get_session_cache_mode. 2011-11-01 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Raise TypeError when trying to check a PKey instance which has no private component, instead of crashing. Based on fix by . 2011-09-14 Žiga Seilnacht * OpenSSL/crypto/crypto.c: Allow exceptions from passphrase callbacks to propagate up out of load_privatekey * OpenSSL/crypto/crypto.c: Raise an exception when a too-long passphrase is returned from a passphrase callback, instead of silently truncating it. * OpenSSL/crypto/crypto.c: Fix a memory leak when a passphrase callback returns the wrong type. 2011-09-13 Jean-Paul Calderone * OpenSSL/crypto/crl.c: Add error handling for the use of X509_CRL_sign. 2011-09-11 Jonathan Ballet * doc/: Convert the LaTeX documentation to Sphinx-using ReST. * OpenSSL/: Convert the epytext API documentation to Sphinx-using ReST. 2011-09-08 Guillermo Gonzalez * OpenSSL/ssl/context.c: Add Context.set_mode method. * OpenSSL/ssl/ssl.c: Add MODE_RELEASE_BUFFERS and OP_NO_COMPRESSION constants. 2011-09-02 Jean-Paul Calderone * Release 0.13 2011-06-12 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Add the PKey.check method, mostly implemented by Rick Dean, to verify the internal consistency of a PKey instance. 2011-06-12 Jean-Paul Calderone * OpenSSL/crypto/crypto.c: Fix the sign and verify functions so they handle data with embedded NULs. Fix by David Brodsky . 2011-05-20 Jean-Paul Calderone * OpenSSL/ssl/connection.c, OpenSSL/test/test_ssl.py: Add a new method to the Connection type, get_peer_cert_chain, for retrieving the peer's certificate chain. 2011-05-19 Jean-Paul Calderone * OpenSSL/crypto/x509.c, OpenSSL/test/test_crypto.py: Add a new method to the X509 type, get_signature_algorithm, for inspecting the signature algorithm field of the certificate. Based on a patch from . 2011-05-10 Jean-Paul Calderone * OpenSSL/crypto/crypto.h: Work around a Windows/OpenSSL 1.0 issue explicitly including a Windows header before any OpenSSL headers. * OpenSSL/crypto/pkcs12.c: Work around an OpenSSL 1.0 issue by explicitly flushing errors known to be uninteresting after calling PKCS12_parse. * OpenSSL/ssl/context.c: Remove SSLv2 support if the underlying OpenSSL library does not provide it. * OpenSSL/test/test_crypto.py: Support an OpenSSL 1.0 change from MD5 to SHA1 by allowing either hash algorithm's result as the return value of X509.subject_name_hash. * OpenSSL/test/test_ssl.py: Support an OpenSSL 1.0 change from MD5 to SHA1 by constructing certificate files named using both hash algorithms' results when testing Context.load_verify_locations. * Support OpenSSL 1.0.0a. 2011-04-15 Jean-Paul Calderone * OpenSSL/ssl/ssl.c: Add OPENSSL_VERSION_NUMBER, SSLeay_version and related constants for retrieving version information about the underlying OpenSSL library. 2011-04-07 Jean-Paul Calderone * Release 0.12 2011-04-06 Jean-Paul Calderone * OpenSSL/crypto/x509.c: Add get_extension_count and get_extension to the X509 type, allowing read access to certificate extensions. * OpenSSL/crypto/x509ext.c: Add get_short_name and get_data to the X509Extension type, allowing read access to the contents of an extension. 2011-03-21 Olivier Hervieu * OpenSSL/ssl/ssl.c: Expose a number of symbolic constants for values passed to the connection "info" callback. 2011-01-22 Jean-Paul Calderone * OpenSSL/ssl/connection.py: Add support for new-style buffers (primarily memoryviews) to Connection.send and Connection.sendall. 2010-11-01 Jean-Paul Calderone * Release 0.11 2010-10-07 Jean-Paul Calderone * Initial support for Python 3.x throughout the codebase. 2010-09-14 Jean-Paul Calderone * OpenSSL/crypto/netscape_spki.c: Fix an off-by-one mistake in the error handling for NetscapeSPKI.verify. Add additional error checking to NetscapeSPKI.sign to handle the case where there is no private key. * OpenSSL/crypto/x509.c: Fix an overflow bug in the subject_name_hash method of the X509 type which would cause it to return negative values on 32 bit systems. * OpenSSL/crypto/x509req.c: Fix an off-by-one mistake in the error handling for X509Req.verify. * OpenSSL/ssl/context.c: Fix the error handling in the load_tmp_dh method of the Context type which would cause it to always raise MemoryError, regardless of the actual error (such as a bad file name). * OpenSSL/test/: Numerous unit tests added, both for above fixes and for other previously untested code paths. 2010-07-27 Jean-Paul Calderone * Re-arrange the repository so that the package can be built and used in-place without requiring installation. 2010-02-27 James Yonan * src/crypto/crypto.c: Added crypto.sign and crypto.verify methods that wrap EVP_Sign and EVP_Verify function families, using code derived from Dave Cridland's PyOpenSSL branch. * test/test_crypto.py: Added unit tests for crypto.sign and crypto.verify. 2010-01-27 Jean-Paul Calderone * src/ssl/connection.c, src/util.h: Apply patch from Sandro Tosi to fix misspellings of "compatibility". 2009-11-13 Jean-Paul Calderone * Release 0.10 2009-11-07 Žiga Seilnacht, Jean-Paul Calderone * src/ssl/connection.c, src/ssl/context.c: Add set_client_ca_list, add_client_ca, and get_client_ca_list to Context for manipulating the list of certificate authority names which are sent by servers with the certificate request message. * src/util.h: Add ssize-related defines if the version of Python being used does not have them. * setup.py: Significant changes to the way Windows builds are done, particularly the way OpenSSL headers and libraries are found (with the new --with-openssl argument to build_ext). 2009-08-27 Rick Dean , Jean-Paul Calderone * src/crypto/pkcs12.c: Add setters to the PKCS12 type for the certificate, private key, ca certificate list, and friendly name, and add a getter for the friendly name. Also add a method for exporting a PKCS12 object as a string. * test/test_crypto.py: Add lots of additional tests for the PKCS12 type. * doc/pyOpenSSL.tex: Documentation for the new PKCS12 methods. 2009-07-17 Rick Dean , Jean-Paul Calderone * src/crypto/x509ext.c: Add subject and issuer parameters to X509Extension, allowing creation of extensions which require that information. Fixes LP#322813. 2009-07-16 Jean-Paul Calderone * test/util.py: Changed the base TestCase's tearDown to assert that no errors were left in the OpenSSL error queue by the test. * src/crypto/crypto.c: Add a private helper in support of the TestCase.tearDown change. * src/crypto/x509name.c: Changed X509Name's getattr implementation to clean up the error queue. Fixes LP#314814. * test/util.c: Changed flush_error_queue to avoid a reference counting bug caused by macro expansion. 2009-07-16 Rick Dean * src/rand.c: Added OpenSSL.rand.bytes to get random bytes directly. * src/util.c: Added generic exceptions_from_error_queue to replace the various other implementations of this function. Also updated the rest of the codebase to use this version instead. 2009-07-05 Jean-Paul Calderone * test/util.py, test/test_ssl.py, test/test_crypto.py: Fold the Python 2.3 compatibility TestCase mixin into the TestCase defined in util.py. 2009-07-05 Jean-Paul Calderone * test/util.py, test/test_ssl.py, test/test_crypto.py: Stop trying to use Twisted's TestCase even when it's available. Instead, always use the stdlib TestCase with a few enhancements. 2009-07-04 Jean-Paul Calderone * Changed most extension types so that they can be instantiated using the type object rather than a factory function. The old factory functions are now aliases for the type objects. Fixes LP#312786. 2009-05-27 Jean-Paul Calderone * Changed all docstrings in extension modules to be friendlier towards Python programmers. Fixes LP#312787. 2009-05-27 Jean-Paul Calderone * src/crypto/x509ext.c: Correctly deallocate the new Extension instance when there is an error initializing it and it is not going to be returned. Resolves LP#368043. 2009-05-11 Jean-Paul Calderone * test/test_crypto.py: Use binary mode for the pipe to talk to the external openssl binary. The data being transported over this pipe is indeed binary, so previously it would often be truncated or otherwise mangled. * src/ssl/connection.h, src/ssl/connection.c, test/test_ssl.py: Extend the Connection class with support for in-memory BIOs. This allows SSL to be run without a real socket, useful for implementing EAP-TLS or using SSL with Windows IO completion ports, for example. Based heavily on contributions from Rick Dean. 2009-04-25 Jean-Paul Calderone * Release 0.9 2009-04-01 Jean-Paul Calderone Samuele Pedroni * src/util.h: Delete the TLS key before trying to set a new value for it in case the current thread identifier is a recycled one (if it is recycled, the key won't be set because there is already a value from the previous thread to have this identifier and to use the pyOpenSSL API). 2009-04-01 Jean-Paul Calderone * src/crypto/crypto.c: Add FILETYPE_TEXT for dumping keys and certificates and certificate signature requests to a text format. 2008-12-31 Jean-Paul Calderone * src/crypto/x509ext.c, test/test_crypto.py: Add the get_short_name method to X509Extension based on patch from Alex Stapleton. 2008-12-31 Jean-Paul Calderone * src/crypto/x509ext.c, test/test_crypto.py: Fix X509Extension so that it is possible to instantiate extensions which use s2i or r2i instead of v2i (an extremely obscure extension implementation detail). 2008-12-30 Jean-Paul Calderone * MANIFEST.in, src/crypto/crypto.c, src/crypto/x509.c, src/crypto/x509name.c, src/rand/rand.c, src/ssl/context.c: Changes which eliminate compiler warnings but should not change any behavior. 2008-12-28 Jean-Paul Calderone * test/test_ssl.py, src/ssl/ssl.c: Expose DTLS-related constants, OP_NO_QUERY_MTU, OP_COOKIE_EXCHANGE, and OP_NO_TICKET. 2008-12-28 Jean-Paul Calderone * src/ssl/context.c: Add a capath parameter to Context.load_verify_locations to allow Python code to specify either or both arguments to the underlying SSL_CTX_load_verify_locations API. * src/ssl/context.c: Add Context.set_default_verify_paths, a wrapper around SSL_CTX_set_default_verify_paths. 2008-12-28 Jean-Paul Calderone * test/test_crypto.py, src/crypto/x509req.c: Added get_version and set_version_methods to X509ReqType based on patch from Wouter van Bommel. Resolves LP#274418. 2008-09-22 Jean-Paul Calderone * Release 0.8 2008-10-19 Jean-Paul Calderone * tsafe.py: Revert the deprecation of the thread-safe Connection wrapper. The Connection class should not segfault if used from multiple threads now, but it generally cannot be relied on to produce correct results if used without the thread-safe wrapper. * doc/pyOpenSSL.tex: Correct the documentation for the set_passwd_cb callback parameter so that it accurately describes the required signature. 2008-09-22 Jean-Paul Calderone * Release 0.8a1 2008-09-21 Jean-Paul Calderone * src/ssl/ssl.h, src/ssl/ssl.c: Add a thread-local storage key which will be used to store and retrieve PyThreadState pointers whenever it is necessary to release or re-acquire the GIL. * src/ssl/context.c: Change global_verify_callback so that it unconditionally manipulates the Python threadstate, rather than checking the tstate field which is now always NULL. 2008-04-26 Jean-Paul Calderone * src/ssl/context.c: Change global_passphrase_callback and global_info_callback so that they acquire the GIL before invoking any CPython APIs and do not release it until after they are finished invoking all of them (based heavily on on patch from Dan Williams). * src/ssl/crypto.c: Initialize OpenSSL thread support so that it is valid to use OpenSSL APIs from more than one thread (based on patch from Dan Williams). * test/test_crypto.py: Add tests for load_privatekey and dump_privatekey when a passphrase or a passphrase callback is supplied. * test/test_ssl.py: Add tests for Context.set_passwd_cb and Context.set_info_callback. 2008-04-11 Jean-Paul Calderone * Release 0.7 2008-03-26 Jean-Paul Calderone * src/crypto/x509name.c: Add X509Name.get_components 2008-03-25 Jean-Paul Calderone * src/crypto/x509name.c: Add hash and der methods to X509Name. * src/crypto/x509.c: Fix a bug in X509.get_notBefore and X509.get_notAfter preventing UTCTIME format timestamps from working. 2008-03-12 Jean-Paul Calderone * Fix coding problems in examples/. Remove keys and certificates and add a note about how to generate new ones. 2008-03-09 Jean-Paul Calderone * src/crypto/x509.c: Add getters and setters for the notBefore and notAfter attributes of X509s. * src/crypto/pkey.h, src/crypto/pkey.c, src/crypto/x509req.c, src/crypto/x509.c: Track the initialized and public/private state of EVP_PKEY structures underlying the crypto_PKeyObj type and reject X509Req signature operations on keys not suitable for the task. 2008-03-06 Jean-Paul Calderone * src/crypto/x509name.c: Fix tp_compare so it only returns -1, 0, or 1. This eliminates a RuntimeWarning emitted by Python. * src/crypto/x509req.c: Fix reference counting for X509Name returned by X509Req.get_subject. This removes a segfault when the subject name outlives the request object. * src/crypto/x509.c: Change get_serial_number and set_serial_number to accept Python longs. * doc/pyOpenSSL.tex: A number of minor corrections. 2008-03-03 Jean-Paul Calderone * src/crypto/crypto.c: Expose X509_verify_cert_error_string. (patch from Victor Stinner) 2008-02-22 Jean-Paul Calderone * src/ssl/connection.c src/ssl/context.c src/ssl/ssl.c: Fix compilation on Windows. (patch from Michael Schneider) 2008-02-21 Jean-Paul Calderone * src/ssl/connection.c: Expose SSL_get_shutdown and SSL_set_shutdown. (patch from James Knight) * src/ssl/ssl.c: Expose SSL_SENT_SHUTDOWN and SSL_RECEIVED_SHUTDOWN. (patch from James Knight) 2008-02-19 Jean-Paul Calderone * src/ssl/context.c: Expose SSL_CTX_add_extra_chain_cert. * src/crypto/x509name.c: Fix memory leaks in __getattr__ and __setattr_ implementations. * src/crypto/x509.c: Fix memory leak in X509.get_pubkey(). * leakcheck/: An attempt at a systematic approach to leak elimination. 2004-08-13 Martin Sjögren * Released version 0.6. 2004-08-11 Martin Sjögren * doc/pyOpenSSL.tex: Updates to the docs. 2004-08-10 Martin Sjögren * src/crypto/x509.c: Add X509.add_extensions based on a patch from Han S. Lee. * src/ssl/ssl.c: Add more SSL_OP_ constants. Patch from Mihai Ibanescu. 2004-08-09 Martin Sjögren * setup.py src/crypto/: Add support for Netscape SPKI extensions based on a patch from Tollef Fog Heen. * src/crypto/crypto.c: Add support for python passphrase callbacks based on a patch from Robert Olson. 2004-08-03 Martin Sjögren * src/ssl/context.c: Applied patch from Frederic Peters to add Context.use_certificate_chain_file. * src/crypto/x509.c: Applid patch from Tollef Fog Heen to add X509.subject_name_hash and X509.digest. 2004-08-02 Martin Sjögren * src/crypto/crypto.c src/ssl/ssl.c: Applied patch from Bastian Kleineidam to fix full names of exceptions. 2004-07-19 Martin Sjögren * doc/pyOpenSSL.tex: Fix the errors regarding X509Name's field names. 2004-07-18 Martin Sjögren * examples/certgen.py: Fixed wrong attributes in doc string, thanks Remy. (SFbug#913315) * __init__.py, setup.py, version.py: Add __version__, as suggested by Ronald Oussoren in SFbug#888729. * examples/proxy.py: Fix typos, thanks Mihai Ibanescu. (SFpatch#895820) 2003-01-09 Martin Sjögren * Use cyclic GC protocol in SSL.Connection, SSL.Context, crypto.PKCS12 and crypto.X509Name. 2002-12-02 Martin Sjögren * tsafe.py: Add some missing methods. 2002-10-06 Martin Sjögren * __init__.py: Import tsafe too! 2002-10-05 Martin Sjögren * src/crypto/x509name.c: Use unicode strings instead of ordinary strings in getattr/setattr. Note that plain ascii strings should still work. 2002-09-17 Martin Sjögren * Released version 0.5.1. 2002-09-09 Martin Sjögren * setup.cfg: Fixed build requirements for rpms. 2002-09-07 Martin Sjögren * src/ssl/connection.c: Fix sendall() method. It segfaulted because it was too generous about giving away the GIL. * Added SecureXMLRPCServer example, contributed by Michal Wallace. 2002-09-06 Martin Sjögren * setup.cfg: Updated the build requirements. * src/ssl/connection.c: Fix includes for AIX. 2002-09-04 Anders Hammarquist * Added type checks in all the other places where we expect specific types of objects passed. 2002-09-04 Martin Sjögren * src/crypto/crypto.c: Added an explicit type check in the dump_* functions, so that they won't die when e.g. None is passed in. 2002-08-25 Martin Sjögren * doc/pyOpenSSL.tex: Docs for PKCS12. 2002-08-24 Martin Sjögren * src/crypto: Added basic PKCS12 support, thanks to Mark Welch 2002-08-16 Martin Sjögren * D'oh! Fixes for python 1.5 and python 2.1. 2002-08-15 Martin Sjögren * Version 0.5. Yay! 2002-07-25 Martin Sjögren * src/ssl/context.c: Added set_options method. * src/ssl/ssl.c: Added constants for Context.set_options method. 2002-07-23 Martin Sjögren * Updated docs * src/ssl/connection.c: Changed the get_cipher_list method to actually return a list! WARNING: This change makes the API incompatible with earlier versions! 2002-07-15 Martin Sjögren * src/ssl/connection.[ch]: Removed the fileno method, it uses the transport object's fileno instead. 2002-07-09 Martin Sjögren * src/crypto/x509.c src/crypto/x509name.c: Fixed segfault bug where you used an X509Name after its X509 had been destroyed. * src/crypto/crypto.[ch] src/crypto/x509req.c src/crypto/x509ext.[ch]: Added X509 Extension support. Thanks to maas-Maarten Zeeman * src/crypto/pkey.c: Added bits() and type() methods. 2002-07-08 Martin Sjögren * src/ssl/connection.c: Moved the contents of setup_ssl into the constructor, thereby fixing some segfault bugs :) * src/ssl/connection.c: Added connect_ex and sendall methods. * src/crypto/x509name.c: Cleaned up comparisons and NID lookup. Thank you Maas-Maarten Zeeman * src/rand/rand.c: Fix RAND_screen import. * src/crypto/crypto.c src/crypto/pkcs7.[ch]: Added PKCS7 management, courtesy of Maas-Maarten Zeeman * src/crypto/x509req.c: Added verify method. 2002-06-17 Martin Sjögren * rpm/, setup.cfg: Added improved RPM-building stuff, thanks to Mihai Ibanescu 2002-06-14 Martin Sjögren * examples/proxy.py: Example code for using OpenSSL through a proxy contributed by Mihai Ibanescu * Updated installation instruction and added them to the TeX manual. 2002-06-13 Martin Sjögren * src/ssl/context.c: Changed global_verify_callback so that it uses PyObject_IsTrue instead of requiring ints. * Added pymemcompat.h to make the memory management uniform and backwards-compatible. * src/util.h: Added conditional definition of PyModule_AddObject and PyModule_AddIntConstant * src/ssl/connection.c: Socket methods are no longer explicitly wrapped. fileno() is the only method the transport layer object HAS to support, but if you want to use connect, accept or sock_shutdown, then the transport layer object has to supply connect, accept and shutdown respectively. 2002-06-12 Martin Sjögren * Changed comments to docstrings that are visible in Python. * src/ssl/connection.c: Added set_connect_state and set_accept_state methods. Thanks to Mark Welch for this. 2002-06-11 Martin Sjögren * src/ssl/connection.c: accept and connect now use SSL_set_accept_state and SSL_set_connect_state respectively, instead of SSL_accept and SSL_connect. * src/ssl/connection.c: Added want_read and want_write methods. 2002-06-05 Martin Sjögren * src/ssl/connection.c: Added error messages for windows. The code is copied from Python's socketmodule.c. Ick. * src/ssl/connection.c: Changed the parameters to the SysCallError. It always has a tuple (number, string) now, even though the number might not always be useful. 2002-04-05 Martin Sjögren * Worked more on the Debian packaging, hopefully the packages are getting into the main Debian archive soon. 2002-01-10 Martin Sjögren * Worked some more on the Debian packaging, it's turning out real nice. * Changed format on this file, I'm going to try to be a bit more verbose about my changes, and this format makes it easier. 2002-01-08 Martin Sjögren * Version 0.4.1 * Added some example code * Added the thread safe Connection object in the 'tsafe' submodule * New Debian packaging 2001-08-09 Martin Sjögren * Version 0.4 * Added a compare function for X509Name structures. * Moved the submodules to separate .so files, with tiny C APIs so they can communicate * Skeletal OpenSSL/__init__.py * Removed the err submodule, use crypto.Error and SSL.Error instead 2001-08-06 Martin Sjögren * Version 0.3 * Added more types for dealing with certificates (X509Store, X509Req, PKey) * Functionality to load private keys, certificates and certificate requests from memory buffers, and store them too * X509 and X509Name objects can now be modified as well, very neat when creating certificates ;) * Added SSL_MODE_AUTO_RETRY to smooth things for blocking sockets * Added a sock_shutdown() method to the Connection type * I don't understand why, but I can't use Py_InitModule() to create submodules in Python 2.0, the interpreter segfaults on the cleanup process when I do. I added a conditional compile on the version number, falling back to my own routine. It would of course be nice to investigate what is happening, but I don't have the time to do so * Do INCREF on the type objects before inserting them in the dictionary, so they will never reach refcount 0 (they are, after all, statically allocated) 2001-07-30 Martin Sjögren * Version 0.2 * Lots of tweaking and comments in the code * Now uses distutils instead of the stupid Setup file * Hacked doc/tools/mkhowto, html generation should now work 2001-07-16 Martin Sjögren * Initial release (0.1, don't expect much from this one :-) pyopenssl-24.2.1/doc/Makefile000066400000000000000000000107721465571700000160550ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyOpenSSL.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyOpenSSL.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pyOpenSSL" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyOpenSSL" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pyopenssl-24.2.1/doc/Quotes000066400000000000000000000001761465571700000156150ustar00rootroot00000000000000< Screwtape> I like how developing against OpenSSL is like a text adventure game with a maze of twisty passages, all alike. % pyopenssl-24.2.1/doc/README000066400000000000000000000002031465571700000152610ustar00rootroot00000000000000This is the pyOpenSSL documentation source. It uses Sphinx. To build the documentation, install Sphinx and run: $ make html pyopenssl-24.2.1/doc/api.rst000066400000000000000000000005471465571700000157170ustar00rootroot00000000000000.. _openssl: :py:mod:`OpenSSL` --- Python interface to OpenSSL ================================================= .. py:module:: OpenSSL :synopsis: Python interface to OpenSSL This package provides a high-level interface to the functions in the OpenSSL library. The following modules are defined: .. toctree:: :maxdepth: 2 api/crypto api/ssl pyopenssl-24.2.1/doc/api/000077500000000000000000000000001465571700000151575ustar00rootroot00000000000000pyopenssl-24.2.1/doc/api/crypto.rst000066400000000000000000000111621465571700000172320ustar00rootroot00000000000000.. _openssl-crypto: :py:mod:`crypto` --- Generic cryptographic module ================================================= .. py:module:: OpenSSL.crypto :synopsis: Generic cryptographic module .. note:: `pyca/cryptography`_ is likely a better choice than using this module. It contains a complete set of cryptographic primitives as well as a significantly better and more powerful X509 API. If necessary you can convert to and from cryptography objects using the ``to_cryptography`` and ``from_cryptography`` methods on ``X509``, ``X509Req``, ``CRL``, and ``PKey``. Elliptic curves --------------- .. autofunction:: get_elliptic_curves .. autofunction:: get_elliptic_curve Serialization and deserialization --------------------------------- The following serialization functions take one of these constants to determine the format. .. py:data:: FILETYPE_PEM :data:`FILETYPE_PEM` serializes data to a Base64-encoded encoded representation of the underlying ASN.1 data structure. This representation includes delimiters that define what data structure is contained within the Base64-encoded block: for example, for a certificate, the delimiters are ``-----BEGIN CERTIFICATE-----`` and ``-----END CERTIFICATE-----``. .. py:data:: FILETYPE_ASN1 :data:`FILETYPE_ASN1` serializes data to the underlying ASN.1 data structure. The format used by :data:`FILETYPE_ASN1` is also sometimes referred to as DER. Certificates ~~~~~~~~~~~~ .. autofunction:: dump_certificate .. autofunction:: load_certificate Certificate signing requests ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: dump_certificate_request .. autofunction:: load_certificate_request Private keys ~~~~~~~~~~~~ .. autofunction:: dump_privatekey .. autofunction:: load_privatekey Public keys ~~~~~~~~~~~ .. autofunction:: dump_publickey .. autofunction:: load_publickey Certificate revocation lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autofunction:: dump_crl .. autofunction:: load_crl Signing and verifying signatures -------------------------------- .. autofunction:: sign .. autofunction:: verify .. _openssl-x509: X509 objects ------------ .. autoclass:: X509 :members: .. _openssl-x509name: X509Name objects ---------------- .. autoclass:: X509Name :members: :special-members: :exclude-members: __repr__, __getattr__, __weakref__ .. _openssl-x509req: X509Req objects --------------- .. autoclass:: X509Req :members: :special-members: :exclude-members: __weakref__ .. _openssl-x509store: X509Store objects ----------------- .. autoclass:: X509Store :members: .. _openssl-x509storecontexterror: X509StoreContextError objects ----------------------------- .. autoclass:: X509StoreContextError :members: .. _openssl-x509storecontext: X509StoreContext objects ------------------------ .. autoclass:: X509StoreContext :members: .. _openssl-pkey: X509StoreFlags constants ------------------------ .. autoclass:: X509StoreFlags .. data:: CRL_CHECK .. data:: CRL_CHECK_ALL .. data:: IGNORE_CRITICAL .. data:: X509_STRICT .. data:: ALLOW_PROXY_CERTS .. data:: POLICY_CHECK .. data:: EXPLICIT_POLICY .. data:: INHIBIT_MAP .. data:: NOTIFY_POLICY .. data:: CHECK_SS_SIGNATURE .. data:: PARTIAL_CHAIN .. _openssl-x509storeflags: PKey objects ------------ .. autoclass:: PKey :members: .. py:data:: TYPE_RSA TYPE_DSA Key type constants. .. _openssl-509ext: X509Extension objects --------------------- .. autoclass:: X509Extension :members: :special-members: :exclude-members: __weakref__ .. _crl: CRL objects ----------- .. autoclass:: CRL :members: :special-members: :exclude-members: __weakref__ .. _revoked: Revoked objects --------------- .. autoclass:: Revoked :members: Exceptions ---------- .. py:exception:: Error Generic exception used in the :py:mod:`.crypto` module. Digest names ------------ Several of the functions and methods in this module take a digest name. These must be strings describing a digest algorithm supported by OpenSSL (by ``EVP_get_digestbyname``, specifically). For example, :const:`b"sha256"` or :const:`b"sha384"`. More information and a list of these digest names can be found in the ``EVP_DigestInit(3)`` man page of your OpenSSL installation. This page can be found online for the latest version of OpenSSL: https://www.openssl.org/docs/manmaster/man3/EVP_DigestInit.html .. _`pyca/cryptography`: https://cryptography.io pyopenssl-24.2.1/doc/api/ssl.rst000066400000000000000000000203651465571700000165200ustar00rootroot00000000000000.. _openssl-ssl: :py:mod:`SSL` --- An interface to the SSL-specific parts of OpenSSL =================================================================== .. py:module:: OpenSSL.SSL :synopsis: An interface to the SSL-specific parts of OpenSSL This module handles things specific to SSL. There are two objects defined: Context, Connection. .. py:data:: TLS_METHOD TLS_SERVER_METHOD TLS_CLIENT_METHOD SSLv2_METHOD SSLv3_METHOD SSLv23_METHOD TLSv1_METHOD TLSv1_1_METHOD TLSv1_2_METHOD These constants represent the different SSL methods to use when creating a context object. New code should only use ``TLS_METHOD``, ``TLS_SERVER_METHOD``, or ``TLS_CLIENT_METHOD``. If the underlying OpenSSL build is missing support for any of these protocols, constructing a :py:class:`Context` using the corresponding :py:const:`*_METHOD` will raise an exception. .. py:data:: SSL3_VERSION TLS1_VERSION TLS1_1_VERSION TLS1_2_VERSION TLS1_3_VERSION These constants represent the different TLS versions to use when setting the minimum or maximum TLS version. .. py:data:: VERIFY_NONE VERIFY_PEER VERIFY_FAIL_IF_NO_PEER_CERT These constants represent the verification mode used by the Context object's :py:meth:`set_verify` method. .. py:data:: FILETYPE_PEM FILETYPE_ASN1 File type constants used with the :py:meth:`use_certificate_file` and :py:meth:`use_privatekey_file` methods of Context objects. .. py:data:: OP_SINGLE_DH_USE OP_SINGLE_ECDH_USE Constants used with :py:meth:`set_options` of Context objects. When these options are used, a new key will always be created when using ephemeral (Elliptic curve) Diffie-Hellman. .. py:data:: OP_EPHEMERAL_RSA Constant used with :py:meth:`set_options` of Context objects. When this option is used, ephemeral RSA keys will always be used when doing RSA operations. .. py:data:: OP_NO_TICKET Constant used with :py:meth:`set_options` of Context objects. When this option is used, the session ticket extension will not be used. .. py:data:: OP_NO_COMPRESSION Constant used with :py:meth:`set_options` of Context objects. When this option is used, compression will not be used. .. py:data:: OP_NO_SSLv2 OP_NO_SSLv3 OP_NO_TLSv1 OP_NO_TLSv1_1 OP_NO_TLSv1_2 OP_NO_TLSv1_3 Constants used with :py:meth:`set_options` of Context objects. Each of these options disables one version of the SSL/TLS protocol. This is interesting if you're using e.g. :py:const:`SSLv23_METHOD` to get an SSLv2-compatible handshake, but don't want to use SSLv2. If the underlying OpenSSL build is missing support for any of these protocols, the :py:const:`OP_NO_*` constant may be undefined. .. py:data:: OPENSSL_VERSION OPENSSL_CFLAGS OPENSSL_BUILT_ON OPENSSL_PLATFORM OPENSSL_DIR .. versionchanged:: 22.1.0 Previously these were all named ``SSLEAY_*``. Those names are still available for backwards compatibility, but the ``OPENSSL_*`` names are preferred. Constants used with :py:meth:`OpenSSL_version` to specify what OpenSSL version information to retrieve. See the man page for the :py:func:`OpenSSL_version` C API for details. .. py:data:: SESS_CACHE_OFF SESS_CACHE_CLIENT SESS_CACHE_SERVER SESS_CACHE_BOTH SESS_CACHE_NO_AUTO_CLEAR SESS_CACHE_NO_INTERNAL_LOOKUP SESS_CACHE_NO_INTERNAL_STORE SESS_CACHE_NO_INTERNAL Constants used with :py:meth:`Context.set_session_cache_mode` to specify the behavior of the session cache and potential session reuse. See the man page for the :py:func:`SSL_CTX_set_session_cache_mode` C API for details. .. versionadded:: 0.14 .. py:data:: OPENSSL_VERSION_NUMBER An integer giving the version number of the OpenSSL library used to build this version of pyOpenSSL. See the man page for the :py:func:`SSLeay_version` C API for details. .. py:data:: NO_OVERLAPPING_PROTOCOLS A sentinel value that can be returned by the callback passed to :py:meth:`Context.set_alpn_select_callback` to indicate that the handshake can continue without a specific application protocol. .. versionadded:: 19.1 .. autofunction:: OpenSSL_version .. py:data:: ContextType See :py:class:`Context`. .. autoclass:: Context :noindex: .. autoclass:: Session .. py:data:: ConnectionType See :py:class:`Connection`. .. py:class:: Connection(context, socket) :noindex: A class representing SSL connections. *context* should be an instance of :py:class:`Context` and *socket* should be a socket [#connection-context-socket]_ object. *socket* may be *None*; in this case, the Connection is created with a memory BIO: see the :py:meth:`bio_read`, :py:meth:`bio_write`, and :py:meth:`bio_shutdown` methods. .. py:exception:: Error This exception is used as a base class for the other SSL-related exceptions, but may also be raised directly. Whenever this exception is raised directly, it has a list of error messages from the OpenSSL error queue, where each item is a tuple *(lib, function, reason)*. Here *lib*, *function* and *reason* are all strings, describing where and what the problem is. See :manpage:`err(3)` for more information. .. py:exception:: ZeroReturnError This exception matches the error return code :py:data:`SSL_ERROR_ZERO_RETURN`, and is raised when the SSL Connection has been closed. In SSL 3.0 and TLS 1.0, this only occurs if a closure alert has occurred in the protocol, i.e. the connection has been closed cleanly. Note that this does not necessarily mean that the transport layer (e.g. a socket) has been closed. It may seem a little strange that this is an exception, but it does match an :py:data:`SSL_ERROR` code, and is very convenient. .. py:exception:: WantReadError The operation did not complete; the same I/O method should be called again later, with the same arguments. Any I/O method can lead to this since new handshakes can occur at any time. The wanted read is for **dirty** data sent over the network, not the **clean** data inside the tunnel. For a socket based SSL connection, **read** means data coming at us over the network. Until that read succeeds, the attempted :py:meth:`OpenSSL.SSL.Connection.recv`, :py:meth:`OpenSSL.SSL.Connection.send`, or :py:meth:`OpenSSL.SSL.Connection.do_handshake` is prevented or incomplete. You probably want to :py:meth:`select()` on the socket before trying again. .. py:exception:: WantWriteError See :py:exc:`WantReadError`. The socket send buffer may be too full to write more data. .. py:exception:: WantX509LookupError The operation did not complete because an application callback has asked to be called again. The I/O method should be called again later, with the same arguments. .. note:: This won't occur in this version, as there are no such callbacks in this version. .. py:exception:: SysCallError The :py:exc:`SysCallError` occurs when there's an I/O error and OpenSSL's error queue does not contain any information. This can mean two things: An error in the transport protocol, or an end of file that violates the protocol. The parameter to the exception is always a pair *(errnum, errstr)*. .. _openssl-context: Context objects --------------- Context objects have the following methods: .. autoclass:: OpenSSL.SSL.Context :members: .. _openssl-session: Session objects --------------- Session objects have no methods. .. _openssl-connection: Connection objects ------------------ Connection objects have the following methods: .. autoclass:: OpenSSL.SSL.Connection :members: .. Rubric:: Footnotes .. [#connection-context-socket] Actually, all that is required is an object that **behaves** like a socket, you could even use files, even though it'd be tricky to get the handshakes right! pyopenssl-24.2.1/doc/backward-compatibility.rst000066400000000000000000000006201465571700000215630ustar00rootroot00000000000000Backward Compatibility ====================== pyOpenSSL has a very strong backward compatibility policy. Generally speaking, you shouldn't ever be afraid of updating. If breaking changes are needed do be done, they are: #. …announced in the :doc:`changelog`. #. …the old behavior raises a :exc:`DeprecationWarning` for a year. #. …are done with another announcement in the :doc:`changelog`. pyopenssl-24.2.1/doc/changelog.rst000066400000000000000000000000361465571700000170660ustar00rootroot00000000000000.. include:: ../CHANGELOG.rst pyopenssl-24.2.1/doc/conf.py000066400000000000000000000200431465571700000157040ustar00rootroot00000000000000# # pyOpenSSL documentation build configuration file, created by # sphinx-quickstart on Sat Jul 16 07:12:22 2011. # # This file is execfile()d with the current directory set to its parent dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import codecs import os import re import sys HERE = os.path.abspath(os.path.dirname(__file__)) def read_file(*parts): """ Build an absolute path from *parts* and return the contents of the resulting file. Assume UTF-8 encoding. """ with codecs.open(os.path.join(HERE, *parts), "rb", "ascii") as f: return f.read() def find_version(*file_paths): version_file = read_file(*file_paths) version_match = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M ) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") DOC_DIR = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.path.abspath(os.path.join(DOC_DIR, ".."))) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = "1.0" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = "pyOpenSSL" authors = "The pyOpenSSL developers" copyright = "2001 " + authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = find_version("..", "src", "OpenSSL", "version.py") # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = "pyOpenSSLdoc" # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letter' or 'a4'). # latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). # latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]) latex_documents = [ ("index", "pyOpenSSL.tex", "pyOpenSSL Documentation", authors, "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Additional stuff for the LaTeX preamble. # latex_preamble = '' # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "pyopenssl", "pyOpenSSL Documentation", [authors], 1)] intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "cryptography": ("https://cryptography.io/en/latest/", None), } pyopenssl-24.2.1/doc/images/000077500000000000000000000000001465571700000156535ustar00rootroot00000000000000pyopenssl-24.2.1/doc/images/pyopenssl-brand.png000066400000000000000000000070641465571700000215100ustar00rootroot00000000000000‰PNG  IHDRÀÀRÜlbKGDÿÿÿ ½§“ pHYs  šœtIMEØ 1 ö$9õ ÁIDATxÚíÝpUåÇñ÷¹ÉÍ/ÈL 4A 2 „ŽÆ‚hdE(¦Йv:.³f-¦{WA0,ÈnYÔ©¶`w*®­ÓuYÃÍ®K5 ©"R~ (#A IÊï’ܳ„NAäæÞsä~^3ü÷ÞçyÎs¾_ÎyÎyÎs@DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDÄBF¸o€ËåŠJMÓœbFÜ›viš¦a À‡çžyæ™j…­€ÊÊÊl›Í¶˜Aûì——/_þþÚµkÏ(|#8\.×PÓ4ÿôÀýö¾a÷»\.BØ7¶0nû+üS=Ï?(|#4ž~úéѦi:#úÐm¯ðаÙl´ëHY¹råuCd&@¶vØívõC„Žâ´ëÔ‘>ñYt„ ÿü8 „Ü¥CÇõ´®À4Í;–J¿Å–a?t¹\ÿ˜¡ÞØ'Ÿ|²"!!áEÓ4‹š:òÇÿü/¸\®MáüUUUJ€šJ_ƒ¿p…[»].W»iš.…¦ÀW\.×…°Ü)6ÛG M%€¯õá.—«I¡©Qˆ(D”"J%€ˆ@D ¢Qˆ(D”"J%€ˆ@D ¢¹A¨®= Ø{³£££ã¢¢¢boU€Çãiokk»®;&66¶ßí¾ÓÖÖvÉãñ´ÝäãÃÀ$…ø­…ê²(6à¦ÐÞÞN{{{w¶­_¸î˜Ë—/wçk ·øÌ¡ðÖ)ˆ@D ¢Qˆ(D”¢PH$‹VtOÿþý5jÉÉÉ$%%ѧOΞ=Ë©S§hiiáÀݽy%J€ðàp8˜>}:yyydffb7Ÿ9råÊöìÙCmm-ï½÷êÀ0ªs¾|¬Êcbb˜={6óæÍ£OŸ>^ÿ¾±±‘­[·R[[Ì><ŒRˆ+¼’‘‘ÁÊ•+IIIñ¹¬Ý»wSUUÅÅ‹•‡¾©S§òüóÏû%ø&L˜À† :t¨:W Úòó󩨨 .ο¯Þ˜  ¼À}fbÝ,ž·€—¯ù{&é¯ún üëz¯KÝé²Ñy‡7b?žçž{Ž+W®XZOcc#ëׯï½iå¶ó·þ»Ç»ºß7á[ƒàþ4xÏdáŽ9ÂæÍ›-=ÕZ½zu ˆï]frý£ùßéQ)µÀôAðÍTøí_ÿ1Z ÐéÝwß%**ŠåË—ã·r[ZZX·nÇŽS'÷Ô àõ«!<Hõê×u€k]ß^‹ŽôS kÕÔÔpøða***ü²˜U]]7n ÖÊp½Kð wC.\)pËkÝ6 C½û7Gå‰'žà³Ï>󩜷ß~›Õ«W+øƒÀ„5ƒ ÷vÁÿ×8ª.»^bb"wÞy§OeŒ3F¼ñøSÍPÓ “»“ûÕe×+--%66Ö§2îºë.¦M›¦Îô—6à û³È§µÍ°£ r•Ý”••ÅÔ©SýRÖâÅ‹‰W§úê$ð=à:ç+¸½úµÓ€ºfxçLì*ö©‡¯: ƒeË–ù­¼°`Áu¬¯ÞáoÏvÿÙ£RfÚ ¾þûÏ£#@WÇÌéÓý>-böìÙ¤¥¥õþÎ3-,ûëѯ=Š`˜^5³À6õÀ=Ñt.¢º›à¯ T ,^¼ØïåÚívÊÊʹ(n@å8r˜›2—G©±©|ò—O¨?WÏϾü™ÿf>ü/Ðô¥ó!Ýkì˜ÌªX߃œ5;o"»™ ü>dz5€s®øÎ™3Dzò+++Ù³gOà6Èâ¹@ÑF4?Îø1?ÊøQFÔ Ÿºxˆâ}Åþ ú)á¾~}ÂÀÉCÔô¤ÈÎéÐNÞÞˆÔÿý‡ ÂÃ?liåååØl½gšÍ£7S9¢²ËàÈì“ÉΉ;×wœÿ*ÙEðÃ1L>èi‘×î‘'¯"1–.]Jt´µ«D6ŒY³fõŠþ*H. |hùm¿c‹aköV솅 ›”áä¢ï àäàŠ´àÏÉÉáÞ{³.lQQ‘õëÀSOuû»ãÇ1+Ù’Ä¿€A93ø?_ ¹þ˜ì¤ X)ÁÅÒ¥KV_ß¾})** ï>#Šñ‰ã½úͤ~“üU}°“WðÍC¼âóXæ†qR‰›ÀóWÇܽVAAAÀßà8cÆ ª««ihhË>?œ„(ï–:ÌîëÕBZ¹ÄÐõ²¦ùøõ ä]Êœl¦sšôëÀåÞü‡ƒ… å¨S^^¶ýöeë—´y¼{Žú³K^M,¼B>­]þñsðw}¸~LðèÕÌ!kƒž¥ð˜•;rÑ¢E=z ¼?Œ7޼¼<êêꬫä§Ÿzñ‹oÓ©ñ—ÍËì»°{÷t»àúó¡» LD¾%2==7õ²dSS?þ8íííVUáÝ["Ýl¡›w_ú/Žy±[Åo=NÖYœï8ßݖ܃“€Ý0‰ÈåÑ—-[ôkò©©©†eÿ½tü%jNvï¾Sùþro‚?à".¦L™Âر¡ñÜüùóÃö=E*â7-7_»æ\Û9–ì[ÂŽS;Bz;"*ìv;¥¥¥!ÓžøøxJJJB¥9^Mg;Ùv’9{æ°pïBÞj~‹†¯:Í­?WϦc›[;–-[,o‡uƒà^hîܹYÿÇùùùlß¾O?ý4ØMéÑSûÛš¶±­iÛÕ¥é{ü~¡#€’’’˜7o^è]…0 –/_ MùÈ÷CˆÏÁ'§•())!...$Û–™™I~~~°›±Øä6ü‹Æ5j<ð@H·±¸¸Øçç}âä PëÝQo›•ðçcŽVž¢ÍŸ??¸pò13Þ¤sIÚ@ ¼ËpòœGÄ xÚ´iŒ92,ÚZXXHMM ÍÍÍÁL‚&`>nlÀ¬{g„ |èsþÆ`! ßÀw‚ãããyùå—ÃêZ{mm-kÖøååŽÞÝ ŽP½úhÁ‚aw£é¾ûî#;;[‘©ðMjj*³gÏ˶÷¶Ç'•APVV†Ýn˶§§§ãt:J€ž?~pâÀ‡ßãs̳B–E¶‘N§E½^•Jŧ¸`‡jþ&/Ué0ÍŒ½j‰d|À5†eY4 ;8J‚%Ú³F›¶×]àn-ǺÄ1ûèqÏ%@0Äår …l°Ã#OÜæfs(²bõÎØ=…ÚpII’΋Å"Š¢P.—Ñuý(ç‚syð:x¡Fu´Ç(°ýËÑ®€½1ÿÓ8õ•²p²[3ÌIEND®B`‚pyopenssl-24.2.1/doc/images/pyopenssl-logo.png000066400000000000000000000027131465571700000213560ustar00rootroot00000000000000‰PNG  IHDR@@ªiqÞbKGDùC» pHYs  šœkIDATxÚí™H”wÇ_w>w¶Éu¦Ó©[ÊÂé²n6df^pY‰‘­óWl#ŒQ IŒÚ´‡LX°Ø*ûMrb›®–”i.æv”Ë™zuWdíôö‡¿N½óλÇsáó‚žïóý|?ßÏçíã÷ó|¿ ™/ƒãããc²³³«€Õ€\„xLMMM{êêê~ò—>­×ë©ÀëÀ"®Øäää*•*à•@­VljL& Yºti¸¿|wìÆb±4]¼xñàñ,}êõú½jµºÈ™ßÿ»ã477loooõf¬Åbù,//¯È›±¾"ÆÂ5–ÄSoÇ>xðàÙ|$/ª¯*’óÀ|³àð¦ h€D€ŽŽŽ ™l¤bY­ÖT ÔÛ@îÞ½;~o±XÒÀÜœo‘¦’ØýtUÎu2 þ_`Á Ú— 34 ÄÆÆ¢P(0™L\ºt‰–––ùÎ{î0 äçç0±±Óh4¤¦¦R__Oyy9ÃÃÃóÿÜ””Daa!cb*ëׯÇl6SUU5;Ço  ˆt|ÀL¦vº8D)}î7fs"@NNŽËäÇÈÌ̤¦¦†Ï‘‹Žníâ#röò©;SÑÁˆˆ4[;AHLLœ­ûwi!iÜ[{žu=ä¿•?U„åž8œx¶°’82%ðÒ创h¹àÚá’%K<Î&88xfƒ´læ‹ñvÉŽÝÕ ÕDFp,îçûÎóðÅñnÎF0ò9:î¢a&úûû=À­m‰èpúš2EÈD[.*„: °#ßÞ?â2å4òûT?r¶„ŽC%ï½½½˜L&·v6›ÖÖV¯ç±Ùm”ýY†Ýnàtïiî<»ãhò&:²Ñ‘Í>¦„Ÿ‰gÑ4!ÙB "¯ÕÕÕÆÂsçÎa6›}š§ôRNþsU€Šæ'ÍØ±O7:ü|KT‰À©h°Žö¼¸•rÂx_Ìä®\¹BEE6›ÍiCCÃìK  n?½MÓ“&çÉ|ŽÜêIß ™QÐÿ5”‡9°XlΞ=K[[Û´çV«•£G2444ÓN'œ‘_-FùÚj໸#¯~û\Ì«ÕjÑjµÓž«T*¶nÝ*ÞDv7ý§‡ ‰_BãM¨+‚9T‹|@@.û³²²÷ýèmðZn­¹EWj;"w87ZìÀVÎÛ… ,åØÔWå§‚Gü*¦7nœñc(00ÜÜ\Ÿç9±ò ª¢_‹¦ò½J"”ÓBÕ˜idÏÕ_è>ÿ:^} QÌŒ‰' ^¸œu5éª[¥R±}ûv·Á§¤¤°bÅ §ë„Ã\‡'a£)!È•o‘R®$LFÏËqó:¾"€Û ãâ‹Û›_`r€*W»wï&##Ã#G]]]×r'|ä·Œ”¡cßX³lyû¢Gšµ}µlº±É±Ô±½»D­ÿË–-#==Ýcû˜˜ôz·1:bulì¿·Ÿ¤ß’H»ž†á†ar)|4ÙÖ/LÚÿ{ÂÎ; ò̸‘* ÓñÑ5Ë5êêbRYíåGŽxâR´íprr2«V­šõ8µZͶmÛ¨¬ôàüó2÷ùˆw‰c1®ÿxv:°Ò‡G§-¢ P(صk—×ã µµµtww»7î.c#nÑÇûäcppP¬œü/ÀóçÏéììôÝÑ< ‹{1æ/à¤Ÿâ»æ?)$$$$$$$$$$$$$$ ÿS€–—m¤eIEND®B`‚pyopenssl-24.2.1/doc/images/pyopenssl.svg000066400000000000000000000204461465571700000204360ustar00rootroot00000000000000 image/svg+xml pyopenssl-24.2.1/doc/index.rst000066400000000000000000000012251465571700000162470ustar00rootroot00000000000000===================================== Welcome to pyOpenSSL's documentation! ===================================== Release v\ |release| (:doc:`What's new? `). pyOpenSSL is a rather thin wrapper around (a subset of) the OpenSSL library. With thin wrapper we mean that a lot of the object methods do nothing more than calling a corresponding function in the OpenSSL library. Contents: ========= .. toctree:: :maxdepth: 2 introduction install api internals Meta ---- .. toctree:: :maxdepth: 1 backward-compatibility changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyopenssl-24.2.1/doc/install.rst000066400000000000000000000000341465571700000166030ustar00rootroot00000000000000.. include:: ../INSTALL.rst pyopenssl-24.2.1/doc/internals.rst000066400000000000000000000061661465571700000171500ustar00rootroot00000000000000.. _internals: Internals ========= We ran into three main problems developing this: Exceptions, callbacks and accessing socket methods. This is what this chapter is about. .. _exceptions: Exceptions ---------- We realized early that most of the exceptions would be raised by the I/O functions of OpenSSL, so it felt natural to mimic OpenSSL's error code system, translating them into Python exceptions. This naturally gives us the exceptions :py:exc:`.SSL.ZeroReturnError`, :py:exc:`.SSL.WantReadError`, :py:exc:`.SSL.WantWriteError`, :py:exc:`.SSL.WantX509LookupError` and :py:exc:`.SSL.SysCallError`. For more information about this, see section :ref:`openssl-ssl`. .. _callbacks: Callbacks --------- Callbacks were more of a problem when pyOpenSSL was written in C. Having switched to being written in Python using cffi, callbacks are now straightforward. The problems that originally existed no longer do (if you are interested in the details you can find descriptions of those problems in the version control history for this document). .. _socket-methods: Accessing Socket Methods ------------------------ We quickly saw the benefit of wrapping socket methods in the :py:class:`.SSL.Connection` class, for an easy transition into using SSL. The problem here is that the :py:mod:`socket` module lacks a C API, and all the methods are declared static. One approach would be to have :py:mod:`.OpenSSL` as a submodule to the :py:mod:`socket` module, placing all the code in ``socketmodule.c``, but this is obviously not a good solution, since you might not want to import tonnes of extra stuff you're not going to use when importing the :py:mod:`socket` module. The other approach is to somehow get a pointer to the method to be called, either the C function, or a callable Python object. This is not really a good solution either, since there's a lot of lookups involved. The way it works is that you have to supply a :py:class:`socket`- **like** transport object to the :py:class:`.SSL.Connection`. The only requirement of this object is that it has a :py:meth:`fileno()` method that returns a file descriptor that's valid at the C level (i.e. you can use the system calls read and write). If you want to use the :py:meth:`connect()` or :py:meth:`accept()` methods of the :py:class:`.SSL.Connection` object, the transport object has to supply such methods too. Apart from them, any method lookups in the :py:class:`.SSL.Connection` object that fail are passed on to the underlying transport object. Future changes might be to allow Python-level transport objects, that instead of having :py:meth:`fileno()` methods, have :py:meth:`read()` and :py:meth:`write()` methods, so more advanced features of Python can be used. This would probably entail some sort of OpenSSL **BIOs**, but converting Python strings back and forth is expensive, so this shouldn't be used unless necessary. Other nice things would be to be able to pass in different transport objects for reading and writing, but then the :py:meth:`fileno()` method of :py:class:`.SSL.Connection` becomes virtually useless. Also, should the method resolution be used on the read-transport or the write-transport? pyopenssl-24.2.1/doc/introduction.rst000066400000000000000000000021741465571700000176650ustar00rootroot00000000000000.. _intro: ============ Introduction ============ History ======= pyOpenSSL was originally created by Martin Sjögren because the SSL support in the standard library in Python 2.1 (the contemporary version of Python when the pyOpenSSL project was begun) was severely limited. Other OpenSSL wrappers for Python at the time were also limited, though in different ways. Later it was maintained by `Jean-Paul Calderone`_ who among other things managed to make pyOpenSSL a pure Python project which the current maintainers are *very* grateful for. Over the time the standard library's ``ssl`` module improved, never reaching the completeness of pyOpenSSL's API coverage. pyOpenSSL remains the only choice for full-featured TLS code in Python versions 3.7+ and PyPy_. Development =========== pyOpenSSL is collaboratively developed by the Python Cryptography Authority (PyCA_) that also maintains the low-level bindings called cryptography_. .. include:: ../CONTRIBUTING.rst .. _Jean-Paul Calderone: https://github.com/exarkun .. _PyPy: http://pypy.org .. _PyCA: https://github.com/pyca .. _cryptography: https://github.com/pyca/cryptography pyopenssl-24.2.1/doc/make.bat000066400000000000000000000106451465571700000160210ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyOpenSSL.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyOpenSSL.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pyopenssl-24.2.1/pyproject.toml000066400000000000000000000014621465571700000165600ustar00rootroot00000000000000[tool.coverage.run] branch = true source = ["OpenSSL", "tests/"] [tool.coverage.paths] source = [ "src/OpenSSL", ".tox/*/lib/python*/site-packages/OpenSSL", ".tox/pypy/site-packages/OpenSSL", ] [tool.coverage.report] exclude_also = [ "assert False", ] show_missing = true [tool.mypy] warn_unused_configs = true follow_imports = "skip" strict = true [[tool.mypy.overrides]] module = "OpenSSL.*" warn_return_any = false [[tool.mypy.overrides]] module = "cryptography.*" ignore_missing_imports = true [[tool.mypy.overrides]] module = "cffi.*" ignore_missing_imports = true [tool.pytest.ini_options] addopts = "-r s --strict-markers" testpaths = ["tests"] [tool.ruff] lint.select = ['E', 'F', 'I', 'W', 'UP', 'RUF'] line-length = 79 [tool.ruff.lint.isort] known-first-party = ["OpenSSL", "tests"] pyopenssl-24.2.1/setup.cfg000066400000000000000000000005471465571700000154700ustar00rootroot00000000000000[metadata] # Ensure LICENSE is included in wheels. license_file = LICENSE # bdist_rpm settings contributed by Mihai Ibanescu # This is currently *not* actively tested. [bdist_rpm] release = 1 build_requires = openssl-devel python-devel python-sphinx group = Development/Libraries build_script = rpm/build_script doc_files = doc/_build/html pyopenssl-24.2.1/setup.py000066400000000000000000000061101465571700000153510ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright (C) Jean-Paul Calderone 2008-2015, All rights reserved # """ Installation script for the OpenSSL package. """ import os import re from setuptools import find_packages, setup HERE = os.path.abspath(os.path.dirname(__file__)) META_PATH = os.path.join("src", "OpenSSL", "version.py") def read_file(*parts): """ Build an absolute path from *parts* and return the contents of the resulting file. Assume UTF-8 encoding. """ with open(os.path.join(HERE, *parts), encoding="utf-8", newline=None) as f: return f.read() META_FILE = read_file(META_PATH) def find_meta(meta): """ Extract __*meta*__ from META_FILE. """ meta_match = re.search( rf"^__{meta}__ = ['\"]([^'\"]*)['\"]", META_FILE, re.M ) if meta_match: return meta_match.group(1) raise RuntimeError(f"Unable to find __{meta}__ string.") URI = find_meta("uri") LONG = ( read_file("README.rst") + "\n\n" + "Release Information\n" + "===================\n\n" + re.search( r"(\d{2}.\d.\d \(.*?\)\n.*?)\n\n\n----\n", read_file("CHANGELOG.rst"), re.S, ).group(1) + "\n\n`Full changelog " + "<{uri}en/stable/changelog.html>`_.\n\n" ).format(uri=URI) if __name__ == "__main__": setup( name=find_meta("title"), version=find_meta("version"), description=find_meta("summary"), long_description=LONG, author=find_meta("author"), author_email=find_meta("email"), url=URI, project_urls={ "Source": "https://github.com/pyca/pyopenssl", }, license=find_meta("license"), classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security :: Cryptography", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Networking", ], python_requires=">=3.7", packages=find_packages(where="src"), package_dir={"": "src"}, install_requires=[ "cryptography>=41.0.5,<44", ], extras_require={ "test": ["pytest-rerunfailures", "pretend", "pytest>=3.0.1"], "docs": [ "sphinx!=5.2.0,!=5.2.0.post0,!=7.2.5", "sphinx_rtd_theme", ], }, ) pyopenssl-24.2.1/src/000077500000000000000000000000001465571700000144305ustar00rootroot00000000000000pyopenssl-24.2.1/src/OpenSSL/000077500000000000000000000000001465571700000157135ustar00rootroot00000000000000pyopenssl-24.2.1/src/OpenSSL/SSL.py000066400000000000000000003205501465571700000167330ustar00rootroot00000000000000import os import socket import typing from errno import errorcode from functools import partial, wraps from itertools import chain, count from sys import platform from typing import Any, Callable, List, Optional, Sequence, Tuple, TypeVar from weakref import WeakValueDictionary from OpenSSL._util import ( StrOrBytesPath as _StrOrBytesPath, ) from OpenSSL._util import ( exception_from_error_queue as _exception_from_error_queue, ) from OpenSSL._util import ( ffi as _ffi, ) from OpenSSL._util import ( lib as _lib, ) from OpenSSL._util import ( make_assert as _make_assert, ) from OpenSSL._util import ( no_zero_allocator as _no_zero_allocator, ) from OpenSSL._util import ( path_bytes as _path_bytes, ) from OpenSSL._util import ( text_to_bytes_and_warn as _text_to_bytes_and_warn, ) from OpenSSL.crypto import ( FILETYPE_PEM, X509, PKey, X509Name, X509Store, _EllipticCurve, _PassphraseHelper, ) __all__ = [ "OPENSSL_VERSION_NUMBER", "SSLEAY_VERSION", "SSLEAY_CFLAGS", "SSLEAY_PLATFORM", "SSLEAY_DIR", "SSLEAY_BUILT_ON", "OPENSSL_VERSION", "OPENSSL_CFLAGS", "OPENSSL_PLATFORM", "OPENSSL_DIR", "OPENSSL_BUILT_ON", "SENT_SHUTDOWN", "RECEIVED_SHUTDOWN", "SSLv23_METHOD", "TLSv1_METHOD", "TLSv1_1_METHOD", "TLSv1_2_METHOD", "TLS_METHOD", "TLS_SERVER_METHOD", "TLS_CLIENT_METHOD", "DTLS_METHOD", "DTLS_SERVER_METHOD", "DTLS_CLIENT_METHOD", "SSL3_VERSION", "TLS1_VERSION", "TLS1_1_VERSION", "TLS1_2_VERSION", "TLS1_3_VERSION", "OP_NO_SSLv2", "OP_NO_SSLv3", "OP_NO_TLSv1", "OP_NO_TLSv1_1", "OP_NO_TLSv1_2", "MODE_RELEASE_BUFFERS", "OP_SINGLE_DH_USE", "OP_SINGLE_ECDH_USE", "OP_EPHEMERAL_RSA", "OP_MICROSOFT_SESS_ID_BUG", "OP_NETSCAPE_CHALLENGE_BUG", "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", "OP_SSLREF2_REUSE_CERT_TYPE_BUG", "OP_MICROSOFT_BIG_SSLV3_BUFFER", "OP_MSIE_SSLV2_RSA_PADDING", "OP_SSLEAY_080_CLIENT_DH_BUG", "OP_TLS_D5_BUG", "OP_TLS_BLOCK_PADDING_BUG", "OP_DONT_INSERT_EMPTY_FRAGMENTS", "OP_CIPHER_SERVER_PREFERENCE", "OP_TLS_ROLLBACK_BUG", "OP_PKCS1_CHECK_1", "OP_PKCS1_CHECK_2", "OP_NETSCAPE_CA_DN_BUG", "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", "OP_NO_COMPRESSION", "OP_NO_QUERY_MTU", "OP_COOKIE_EXCHANGE", "OP_NO_TICKET", "OP_ALL", "VERIFY_PEER", "VERIFY_FAIL_IF_NO_PEER_CERT", "VERIFY_CLIENT_ONCE", "VERIFY_NONE", "SESS_CACHE_OFF", "SESS_CACHE_CLIENT", "SESS_CACHE_SERVER", "SESS_CACHE_BOTH", "SESS_CACHE_NO_AUTO_CLEAR", "SESS_CACHE_NO_INTERNAL_LOOKUP", "SESS_CACHE_NO_INTERNAL_STORE", "SESS_CACHE_NO_INTERNAL", "SSL_ST_CONNECT", "SSL_ST_ACCEPT", "SSL_ST_MASK", "SSL_CB_LOOP", "SSL_CB_EXIT", "SSL_CB_READ", "SSL_CB_WRITE", "SSL_CB_ALERT", "SSL_CB_READ_ALERT", "SSL_CB_WRITE_ALERT", "SSL_CB_ACCEPT_LOOP", "SSL_CB_ACCEPT_EXIT", "SSL_CB_CONNECT_LOOP", "SSL_CB_CONNECT_EXIT", "SSL_CB_HANDSHAKE_START", "SSL_CB_HANDSHAKE_DONE", "Error", "WantReadError", "WantWriteError", "WantX509LookupError", "ZeroReturnError", "SysCallError", "NO_OVERLAPPING_PROTOCOLS", "SSLeay_version", "Session", "Context", "Connection", "X509VerificationCodes", ] OPENSSL_VERSION_NUMBER: int = _lib.OPENSSL_VERSION_NUMBER OPENSSL_VERSION: int = _lib.OPENSSL_VERSION OPENSSL_CFLAGS: int = _lib.OPENSSL_CFLAGS OPENSSL_PLATFORM: int = _lib.OPENSSL_PLATFORM OPENSSL_DIR: int = _lib.OPENSSL_DIR OPENSSL_BUILT_ON: int = _lib.OPENSSL_BUILT_ON SSLEAY_VERSION = OPENSSL_VERSION SSLEAY_CFLAGS = OPENSSL_CFLAGS SSLEAY_PLATFORM = OPENSSL_PLATFORM SSLEAY_DIR = OPENSSL_DIR SSLEAY_BUILT_ON = OPENSSL_BUILT_ON SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN SSLv23_METHOD = 3 TLSv1_METHOD = 4 TLSv1_1_METHOD = 5 TLSv1_2_METHOD = 6 TLS_METHOD = 7 TLS_SERVER_METHOD = 8 TLS_CLIENT_METHOD = 9 DTLS_METHOD = 10 DTLS_SERVER_METHOD = 11 DTLS_CLIENT_METHOD = 12 SSL3_VERSION: int = _lib.SSL3_VERSION TLS1_VERSION: int = _lib.TLS1_VERSION TLS1_1_VERSION: int = _lib.TLS1_1_VERSION TLS1_2_VERSION: int = _lib.TLS1_2_VERSION TLS1_3_VERSION: int = _lib.TLS1_3_VERSION OP_NO_SSLv2: int = _lib.SSL_OP_NO_SSLv2 OP_NO_SSLv3: int = _lib.SSL_OP_NO_SSLv3 OP_NO_TLSv1: int = _lib.SSL_OP_NO_TLSv1 OP_NO_TLSv1_1: int = _lib.SSL_OP_NO_TLSv1_1 OP_NO_TLSv1_2: int = _lib.SSL_OP_NO_TLSv1_2 try: OP_NO_TLSv1_3: int = _lib.SSL_OP_NO_TLSv1_3 __all__.append("OP_NO_TLSv1_3") except AttributeError: pass MODE_RELEASE_BUFFERS: int = _lib.SSL_MODE_RELEASE_BUFFERS OP_SINGLE_DH_USE: int = _lib.SSL_OP_SINGLE_DH_USE OP_SINGLE_ECDH_USE: int = _lib.SSL_OP_SINGLE_ECDH_USE OP_EPHEMERAL_RSA: int = _lib.SSL_OP_EPHEMERAL_RSA OP_MICROSOFT_SESS_ID_BUG: int = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG OP_NETSCAPE_CHALLENGE_BUG: int = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: int = ( _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG ) OP_SSLREF2_REUSE_CERT_TYPE_BUG: int = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG OP_MICROSOFT_BIG_SSLV3_BUFFER: int = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER OP_MSIE_SSLV2_RSA_PADDING: int = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING OP_SSLEAY_080_CLIENT_DH_BUG: int = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG OP_TLS_D5_BUG: int = _lib.SSL_OP_TLS_D5_BUG OP_TLS_BLOCK_PADDING_BUG: int = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG OP_DONT_INSERT_EMPTY_FRAGMENTS: int = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS OP_CIPHER_SERVER_PREFERENCE: int = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE OP_TLS_ROLLBACK_BUG: int = _lib.SSL_OP_TLS_ROLLBACK_BUG OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 OP_PKCS1_CHECK_2: int = _lib.SSL_OP_PKCS1_CHECK_2 OP_NETSCAPE_CA_DN_BUG: int = _lib.SSL_OP_NETSCAPE_CA_DN_BUG OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: int = ( _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG ) OP_NO_COMPRESSION: int = _lib.SSL_OP_NO_COMPRESSION OP_NO_QUERY_MTU: int = _lib.SSL_OP_NO_QUERY_MTU OP_COOKIE_EXCHANGE: int = _lib.SSL_OP_COOKIE_EXCHANGE OP_NO_TICKET: int = _lib.SSL_OP_NO_TICKET try: OP_NO_RENEGOTIATION: int = _lib.SSL_OP_NO_RENEGOTIATION __all__.append("OP_NO_RENEGOTIATION") except AttributeError: pass try: OP_IGNORE_UNEXPECTED_EOF: int = _lib.SSL_OP_IGNORE_UNEXPECTED_EOF __all__.append("OP_IGNORE_UNEXPECTED_EOF") except AttributeError: pass try: OP_LEGACY_SERVER_CONNECT: int = _lib.SSL_OP_LEGACY_SERVER_CONNECT __all__.append("OP_LEGACY_SERVER_CONNECT") except AttributeError: pass OP_ALL: int = _lib.SSL_OP_ALL VERIFY_PEER: int = _lib.SSL_VERIFY_PEER VERIFY_FAIL_IF_NO_PEER_CERT: int = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT VERIFY_CLIENT_ONCE: int = _lib.SSL_VERIFY_CLIENT_ONCE VERIFY_NONE: int = _lib.SSL_VERIFY_NONE SESS_CACHE_OFF: int = _lib.SSL_SESS_CACHE_OFF SESS_CACHE_CLIENT: int = _lib.SSL_SESS_CACHE_CLIENT SESS_CACHE_SERVER: int = _lib.SSL_SESS_CACHE_SERVER SESS_CACHE_BOTH: int = _lib.SSL_SESS_CACHE_BOTH SESS_CACHE_NO_AUTO_CLEAR: int = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR SESS_CACHE_NO_INTERNAL_LOOKUP: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP SESS_CACHE_NO_INTERNAL_STORE: int = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE SESS_CACHE_NO_INTERNAL: int = _lib.SSL_SESS_CACHE_NO_INTERNAL SSL_ST_CONNECT: int = _lib.SSL_ST_CONNECT SSL_ST_ACCEPT: int = _lib.SSL_ST_ACCEPT SSL_ST_MASK: int = _lib.SSL_ST_MASK SSL_CB_LOOP: int = _lib.SSL_CB_LOOP SSL_CB_EXIT: int = _lib.SSL_CB_EXIT SSL_CB_READ: int = _lib.SSL_CB_READ SSL_CB_WRITE: int = _lib.SSL_CB_WRITE SSL_CB_ALERT: int = _lib.SSL_CB_ALERT SSL_CB_READ_ALERT: int = _lib.SSL_CB_READ_ALERT SSL_CB_WRITE_ALERT: int = _lib.SSL_CB_WRITE_ALERT SSL_CB_ACCEPT_LOOP: int = _lib.SSL_CB_ACCEPT_LOOP SSL_CB_ACCEPT_EXIT: int = _lib.SSL_CB_ACCEPT_EXIT SSL_CB_CONNECT_LOOP: int = _lib.SSL_CB_CONNECT_LOOP SSL_CB_CONNECT_EXIT: int = _lib.SSL_CB_CONNECT_EXIT SSL_CB_HANDSHAKE_START: int = _lib.SSL_CB_HANDSHAKE_START SSL_CB_HANDSHAKE_DONE: int = _lib.SSL_CB_HANDSHAKE_DONE _T = TypeVar("_T") class _NoOverlappingProtocols: pass NO_OVERLAPPING_PROTOCOLS = _NoOverlappingProtocols() # Callback types. _ALPNSelectCallback = Callable[ [ "Connection", typing.Union[List[bytes], _NoOverlappingProtocols], ], None, ] _CookieGenerateCallback = Callable[["Connection"], bytes] _CookieVerifyCallback = Callable[["Connection", bytes], bool] _OCSPClientCallback = Callable[["Connection", bytes, Optional[_T]], bool] _OCSPServerCallback = Callable[["Connection", Optional[_T]], bytes] _PassphraseCallback = Callable[[int, bool, Optional[_T]], bytes] _VerifyCallback = Callable[["Connection", X509, int, int, int], bool] class X509VerificationCodes: """ Success and error codes for X509 verification, as returned by the underlying ``X509_STORE_CTX_get_error()`` function and passed by pyOpenSSL to verification callback functions. See `OpenSSL Verification Errors `_ for details. """ OK = _lib.X509_V_OK ERR_UNABLE_TO_GET_ISSUER_CERT = _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ERR_UNABLE_TO_GET_CRL = _lib.X509_V_ERR_UNABLE_TO_GET_CRL ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = ( _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE ) ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = ( _lib.X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE ) ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = ( _lib.X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY ) ERR_CERT_SIGNATURE_FAILURE = _lib.X509_V_ERR_CERT_SIGNATURE_FAILURE ERR_CRL_SIGNATURE_FAILURE = _lib.X509_V_ERR_CRL_SIGNATURE_FAILURE ERR_CERT_NOT_YET_VALID = _lib.X509_V_ERR_CERT_NOT_YET_VALID ERR_CERT_HAS_EXPIRED = _lib.X509_V_ERR_CERT_HAS_EXPIRED ERR_CRL_NOT_YET_VALID = _lib.X509_V_ERR_CRL_NOT_YET_VALID ERR_CRL_HAS_EXPIRED = _lib.X509_V_ERR_CRL_HAS_EXPIRED ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = ( _lib.X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD ) ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = ( _lib.X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD ) ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = ( _lib.X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD ) ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = ( _lib.X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD ) ERR_OUT_OF_MEM = _lib.X509_V_ERR_OUT_OF_MEM ERR_DEPTH_ZERO_SELF_SIGNED_CERT = ( _lib.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ) ERR_SELF_SIGNED_CERT_IN_CHAIN = _lib.X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = ( _lib.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ) ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = ( _lib.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE ) ERR_CERT_CHAIN_TOO_LONG = _lib.X509_V_ERR_CERT_CHAIN_TOO_LONG ERR_CERT_REVOKED = _lib.X509_V_ERR_CERT_REVOKED ERR_INVALID_CA = _lib.X509_V_ERR_INVALID_CA ERR_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PATH_LENGTH_EXCEEDED ERR_INVALID_PURPOSE = _lib.X509_V_ERR_INVALID_PURPOSE ERR_CERT_UNTRUSTED = _lib.X509_V_ERR_CERT_UNTRUSTED ERR_CERT_REJECTED = _lib.X509_V_ERR_CERT_REJECTED ERR_SUBJECT_ISSUER_MISMATCH = _lib.X509_V_ERR_SUBJECT_ISSUER_MISMATCH ERR_AKID_SKID_MISMATCH = _lib.X509_V_ERR_AKID_SKID_MISMATCH ERR_AKID_ISSUER_SERIAL_MISMATCH = ( _lib.X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH ) ERR_KEYUSAGE_NO_CERTSIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CERTSIGN ERR_UNABLE_TO_GET_CRL_ISSUER = _lib.X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER ERR_UNHANDLED_CRITICAL_EXTENSION = ( _lib.X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION ) ERR_KEYUSAGE_NO_CRL_SIGN = _lib.X509_V_ERR_KEYUSAGE_NO_CRL_SIGN ERR_UNHANDLED_CRITICAL_CRL_EXTENSION = ( _lib.X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION ) ERR_INVALID_NON_CA = _lib.X509_V_ERR_INVALID_NON_CA ERR_PROXY_PATH_LENGTH_EXCEEDED = _lib.X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE = ( _lib.X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE ) ERR_PROXY_CERTIFICATES_NOT_ALLOWED = ( _lib.X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED ) ERR_INVALID_EXTENSION = _lib.X509_V_ERR_INVALID_EXTENSION ERR_INVALID_POLICY_EXTENSION = _lib.X509_V_ERR_INVALID_POLICY_EXTENSION ERR_NO_EXPLICIT_POLICY = _lib.X509_V_ERR_NO_EXPLICIT_POLICY ERR_DIFFERENT_CRL_SCOPE = _lib.X509_V_ERR_DIFFERENT_CRL_SCOPE ERR_UNSUPPORTED_EXTENSION_FEATURE = ( _lib.X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE ) ERR_UNNESTED_RESOURCE = _lib.X509_V_ERR_UNNESTED_RESOURCE ERR_PERMITTED_VIOLATION = _lib.X509_V_ERR_PERMITTED_VIOLATION ERR_EXCLUDED_VIOLATION = _lib.X509_V_ERR_EXCLUDED_VIOLATION ERR_SUBTREE_MINMAX = _lib.X509_V_ERR_SUBTREE_MINMAX ERR_UNSUPPORTED_CONSTRAINT_TYPE = ( _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE ) ERR_UNSUPPORTED_CONSTRAINT_SYNTAX = ( _lib.X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX ) ERR_UNSUPPORTED_NAME_SYNTAX = _lib.X509_V_ERR_UNSUPPORTED_NAME_SYNTAX ERR_CRL_PATH_VALIDATION_ERROR = _lib.X509_V_ERR_CRL_PATH_VALIDATION_ERROR ERR_HOSTNAME_MISMATCH = _lib.X509_V_ERR_HOSTNAME_MISMATCH ERR_EMAIL_MISMATCH = _lib.X509_V_ERR_EMAIL_MISMATCH ERR_IP_ADDRESS_MISMATCH = _lib.X509_V_ERR_IP_ADDRESS_MISMATCH ERR_APPLICATION_VERIFICATION = _lib.X509_V_ERR_APPLICATION_VERIFICATION # Taken from https://golang.org/src/crypto/x509/root_linux.go _CERTIFICATE_FILE_LOCATIONS = [ "/etc/ssl/certs/ca-certificates.crt", # Debian/Ubuntu/Gentoo etc. "/etc/pki/tls/certs/ca-bundle.crt", # Fedora/RHEL 6 "/etc/ssl/ca-bundle.pem", # OpenSUSE "/etc/pki/tls/cacert.pem", # OpenELEC "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", # CentOS/RHEL 7 ] _CERTIFICATE_PATH_LOCATIONS = [ "/etc/ssl/certs", # SLES10/SLES11 ] # These values are compared to output from cffi's ffi.string so they must be # byte strings. _CRYPTOGRAPHY_MANYLINUX_CA_DIR = b"/opt/pyca/cryptography/openssl/certs" _CRYPTOGRAPHY_MANYLINUX_CA_FILE = b"/opt/pyca/cryptography/openssl/cert.pem" class Error(Exception): """ An error occurred in an `OpenSSL.SSL` API. """ _raise_current_error = partial(_exception_from_error_queue, Error) _openssl_assert = _make_assert(Error) class WantReadError(Error): pass class WantWriteError(Error): pass class WantX509LookupError(Error): pass class ZeroReturnError(Error): pass class SysCallError(Error): pass class _CallbackExceptionHelper: """ A base class for wrapper classes that allow for intelligent exception handling in OpenSSL callbacks. :ivar list _problems: Any exceptions that occurred while executing in a context where they could not be raised in the normal way. Typically this is because OpenSSL has called into some Python code and requires a return value. The exceptions are saved to be raised later when it is possible to do so. """ def __init__(self) -> None: self._problems: List[Exception] = [] def raise_if_problem(self) -> None: """ Raise an exception from the OpenSSL error queue or that was previously captured whe running a callback. """ if self._problems: try: _raise_current_error() except Error: pass raise self._problems.pop(0) class _VerifyHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as a certificate verification callback. """ def __init__(self, callback: _VerifyCallback) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ok, store_ctx): # type: ignore[no-untyped-def] x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) _lib.X509_up_ref(x509) cert = X509._from_raw_x509_ptr(x509) error_number = _lib.X509_STORE_CTX_get_error(store_ctx) error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) connection = Connection._reverse_mapping[ssl] try: result = callback( connection, cert, error_number, error_depth, ok ) except Exception as e: self._problems.append(e) return 0 else: if result: _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) return 1 else: return 0 self.callback = _ffi.callback( "int (*)(int, X509_STORE_CTX *)", wrapper ) class _ALPNSelectHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an ALPN selection callback. """ def __init__(self, callback: _ALPNSelectCallback) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, out, outlen, in_, inlen, arg): # type: ignore[no-untyped-def] try: conn = Connection._reverse_mapping[ssl] # The string passed to us is made up of multiple # length-prefixed bytestrings. We need to split that into a # list. instr = _ffi.buffer(in_, inlen)[:] protolist = [] while instr: encoded_len = instr[0] proto = instr[1 : encoded_len + 1] protolist.append(proto) instr = instr[encoded_len + 1 :] # Call the callback outbytes = callback(conn, protolist) any_accepted = True if outbytes is NO_OVERLAPPING_PROTOCOLS: outbytes = b"" any_accepted = False elif not isinstance(outbytes, bytes): raise TypeError( "ALPN callback must return a bytestring or the " "special NO_OVERLAPPING_PROTOCOLS sentinel value." ) # Save our callback arguments on the connection object to make # sure that they don't get freed before OpenSSL can use them. # Then, return them in the appropriate output parameters. conn._alpn_select_callback_args = [ _ffi.new("unsigned char *", len(outbytes)), _ffi.new("unsigned char[]", outbytes), ] outlen[0] = conn._alpn_select_callback_args[0][0] out[0] = conn._alpn_select_callback_args[1] if not any_accepted: return _lib.SSL_TLSEXT_ERR_NOACK return _lib.SSL_TLSEXT_ERR_OK except Exception as e: self._problems.append(e) return _lib.SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( ( "int (*)(SSL *, unsigned char **, unsigned char *, " "const unsigned char *, unsigned int, void *)" ), wrapper, ) class _OCSPServerCallbackHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an OCSP callback for the server side. Annoyingly, OpenSSL defines one OCSP callback but uses it in two different ways. For servers, that callback is expected to retrieve some OCSP data and hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback is expected to check the OCSP data, and returns a negative value on error, 0 if the response is not acceptable, or positive if it is. These are mutually exclusive return code behaviours, and they mean that we need two helpers so that we always return an appropriate error code if the user's code throws an exception. Given that we have to have two helpers anyway, these helpers are a bit more helpery than most: specifically, they hide a few more of the OpenSSL functions so that the user has an easier time writing these callbacks. This helper implements the server side. """ def __init__(self, callback: _OCSPServerCallback[Any]) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, cdata): # type: ignore[no-untyped-def] try: conn = Connection._reverse_mapping[ssl] # Extract the data if any was provided. if cdata != _ffi.NULL: data = _ffi.from_handle(cdata) else: data = None # Call the callback. ocsp_data = callback(conn, data) if not isinstance(ocsp_data, bytes): raise TypeError("OCSP callback must return a bytestring.") # If the OCSP data was provided, we will pass it to OpenSSL. # However, we have an early exit here: if no OCSP data was # provided we will just exit out and tell OpenSSL that there # is nothing to do. if not ocsp_data: return 3 # SSL_TLSEXT_ERR_NOACK # OpenSSL takes ownership of this data and expects it to have # been allocated by OPENSSL_malloc. ocsp_data_length = len(ocsp_data) data_ptr = _lib.OPENSSL_malloc(ocsp_data_length) _ffi.buffer(data_ptr, ocsp_data_length)[:] = ocsp_data _lib.SSL_set_tlsext_status_ocsp_resp( ssl, data_ptr, ocsp_data_length ) return 0 except Exception as e: self._problems.append(e) return 2 # SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) class _OCSPClientCallbackHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an OCSP callback for the client side. Annoyingly, OpenSSL defines one OCSP callback but uses it in two different ways. For servers, that callback is expected to retrieve some OCSP data and hand it to OpenSSL, and may return only SSL_TLSEXT_ERR_OK, SSL_TLSEXT_ERR_FATAL, and SSL_TLSEXT_ERR_NOACK. For clients, that callback is expected to check the OCSP data, and returns a negative value on error, 0 if the response is not acceptable, or positive if it is. These are mutually exclusive return code behaviours, and they mean that we need two helpers so that we always return an appropriate error code if the user's code throws an exception. Given that we have to have two helpers anyway, these helpers are a bit more helpery than most: specifically, they hide a few more of the OpenSSL functions so that the user has an easier time writing these callbacks. This helper implements the client side. """ def __init__(self, callback: _OCSPClientCallback[Any]) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, cdata): # type: ignore[no-untyped-def] try: conn = Connection._reverse_mapping[ssl] # Extract the data if any was provided. if cdata != _ffi.NULL: data = _ffi.from_handle(cdata) else: data = None # Get the OCSP data. ocsp_ptr = _ffi.new("unsigned char **") ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) if ocsp_len < 0: # No OCSP data. ocsp_data = b"" else: # Copy the OCSP data, then pass it to the callback. ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] valid = callback(conn, ocsp_data, data) # Return 1 on success or 0 on error. return int(bool(valid)) except Exception as e: self._problems.append(e) # Return negative value if an exception is hit. return -1 self.callback = _ffi.callback("int (*)(SSL *, void *)", wrapper) class _CookieGenerateCallbackHelper(_CallbackExceptionHelper): def __init__(self, callback: _CookieGenerateCallback) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, out, outlen): # type: ignore[no-untyped-def] try: conn = Connection._reverse_mapping[ssl] cookie = callback(conn) out[0 : len(cookie)] = cookie outlen[0] = len(cookie) return 1 except Exception as e: self._problems.append(e) # "a zero return value can be used to abort the handshake" return 0 self.callback = _ffi.callback( "int (*)(SSL *, unsigned char *, unsigned int *)", wrapper, ) class _CookieVerifyCallbackHelper(_CallbackExceptionHelper): def __init__(self, callback: _CookieVerifyCallback) -> None: _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, c_cookie, cookie_len): # type: ignore[no-untyped-def] try: conn = Connection._reverse_mapping[ssl] return callback(conn, bytes(c_cookie[0:cookie_len])) except Exception as e: self._problems.append(e) return 0 self.callback = _ffi.callback( "int (*)(SSL *, unsigned char *, unsigned int)", wrapper, ) def _asFileDescriptor(obj: Any) -> int: fd = None if not isinstance(obj, int): meth = getattr(obj, "fileno", None) if meth is not None: obj = meth() if isinstance(obj, int): fd = obj if not isinstance(fd, int): raise TypeError("argument must be an int, or have a fileno() method.") elif fd < 0: raise ValueError( "file descriptor cannot be a negative integer (%i)" % (fd,) ) return fd def OpenSSL_version(type: int) -> bytes: """ Return a string describing the version of OpenSSL in use. :param type: One of the :const:`OPENSSL_` constants defined in this module. """ return _ffi.string(_lib.OpenSSL_version(type)) SSLeay_version = OpenSSL_version def _make_requires(flag: int, error: str) -> Callable[[_T], _T]: """ Builds a decorator that ensures that functions that rely on OpenSSL functions that are not present in this build raise NotImplementedError, rather than AttributeError coming out of cryptography. :param flag: A cryptography flag that guards the functions, e.g. ``Cryptography_HAS_NEXTPROTONEG``. :param error: The string to be used in the exception if the flag is false. """ def _requires_decorator(func): # type: ignore[no-untyped-def] if not flag: @wraps(func) def explode(*args, **kwargs): # type: ignore[no-untyped-def] raise NotImplementedError(error) return explode else: return func return _requires_decorator _requires_alpn = _make_requires( _lib.Cryptography_HAS_ALPN, "ALPN not available" ) _requires_keylog = _make_requires( getattr(_lib, "Cryptography_HAS_KEYLOG", 0), "Key logging not available" ) class Session: """ A class representing an SSL session. A session defines certain connection parameters which may be re-used to speed up the setup of subsequent connections. .. versionadded:: 0.14 """ _session: Any class Context: """ :class:`OpenSSL.SSL.Context` instances define the parameters for setting up new SSL connections. :param method: One of TLS_METHOD, TLS_CLIENT_METHOD, TLS_SERVER_METHOD, DTLS_METHOD, DTLS_CLIENT_METHOD, or DTLS_SERVER_METHOD. SSLv23_METHOD, TLSv1_METHOD, etc. are deprecated and should not be used. """ _methods: typing.ClassVar[ typing.Dict[int, typing.Tuple[Callable[[], Any], Optional[int]]] ] = { SSLv23_METHOD: (_lib.TLS_method, None), TLSv1_METHOD: (_lib.TLS_method, TLS1_VERSION), TLSv1_1_METHOD: (_lib.TLS_method, TLS1_1_VERSION), TLSv1_2_METHOD: (_lib.TLS_method, TLS1_2_VERSION), TLS_METHOD: (_lib.TLS_method, None), TLS_SERVER_METHOD: (_lib.TLS_server_method, None), TLS_CLIENT_METHOD: (_lib.TLS_client_method, None), DTLS_METHOD: (_lib.DTLS_method, None), DTLS_SERVER_METHOD: (_lib.DTLS_server_method, None), DTLS_CLIENT_METHOD: (_lib.DTLS_client_method, None), } def __init__(self, method: int) -> None: if not isinstance(method, int): raise TypeError("method must be an integer") try: method_func, version = self._methods[method] except KeyError: raise ValueError("No such protocol") method_obj = method_func() _openssl_assert(method_obj != _ffi.NULL) context = _lib.SSL_CTX_new(method_obj) _openssl_assert(context != _ffi.NULL) context = _ffi.gc(context, _lib.SSL_CTX_free) self._context = context self._passphrase_helper: Optional[_PassphraseHelper] = None self._passphrase_callback: Optional[_PassphraseCallback[Any]] = None self._passphrase_userdata: Optional[Any] = None self._verify_helper: Optional[_VerifyHelper] = None self._verify_callback: Optional[_VerifyCallback] = None self._info_callback = None self._keylog_callback = None self._tlsext_servername_callback = None self._app_data = None self._alpn_select_helper: Optional[_ALPNSelectHelper] = None self._alpn_select_callback: Optional[_ALPNSelectCallback] = None self._ocsp_helper: typing.Union[ _OCSPClientCallbackHelper, _OCSPServerCallbackHelper, None ] = None self._ocsp_callback: typing.Union[ _OCSPClientCallback[Any], _OCSPServerCallback[Any], None ] = None self._ocsp_data: Optional[Any] = None self._cookie_generate_helper: Optional[ _CookieGenerateCallbackHelper ] = None self._cookie_verify_helper: Optional[_CookieVerifyCallbackHelper] = ( None ) self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE) if version is not None: self.set_min_proto_version(version) self.set_max_proto_version(version) def set_min_proto_version(self, version: int) -> None: """ Set the minimum supported protocol version. Setting the minimum version to 0 will enable protocol versions down to the lowest version supported by the library. If the underlying OpenSSL build is missing support for the selected version, this method will raise an exception. """ _openssl_assert( _lib.SSL_CTX_set_min_proto_version(self._context, version) == 1 ) def set_max_proto_version(self, version: int) -> None: """ Set the maximum supported protocol version. Setting the maximum version to 0 will enable protocol versions up to the highest version supported by the library. If the underlying OpenSSL build is missing support for the selected version, this method will raise an exception. """ _openssl_assert( _lib.SSL_CTX_set_max_proto_version(self._context, version) == 1 ) def load_verify_locations( self, cafile: Optional[_StrOrBytesPath], capath: Optional[_StrOrBytesPath] = None, ) -> None: """ Let SSL know where we can find trusted certificates for the certificate chain. Note that the certificates have to be in PEM format. If capath is passed, it must be a directory prepared using the ``c_rehash`` tool included with OpenSSL. Either, but not both, of *pemfile* or *capath* may be :data:`None`. :param cafile: In which file we can find the certificates (``bytes`` or ``str``). :param capath: In which directory we can find the certificates (``bytes`` or ``str``). :return: None """ if cafile is None: cafile = _ffi.NULL else: cafile = _path_bytes(cafile) if capath is None: capath = _ffi.NULL else: capath = _path_bytes(capath) load_result = _lib.SSL_CTX_load_verify_locations( self._context, cafile, capath ) if not load_result: _raise_current_error() def _wrap_callback( self, callback: _PassphraseCallback[_T] ) -> _PassphraseHelper: @wraps(callback) def wrapper(size: int, verify: bool, userdata: Any) -> bytes: return callback(size, verify, self._passphrase_userdata) return _PassphraseHelper( FILETYPE_PEM, wrapper, more_args=True, truncate=True ) def set_passwd_cb( self, callback: _PassphraseCallback[_T], userdata: Optional[_T] = None, ) -> None: """ Set the passphrase callback. This function will be called when a private key with a passphrase is loaded. :param callback: The Python callback to use. This must accept three positional arguments. First, an integer giving the maximum length of the passphrase it may return. If the returned passphrase is longer than this, it will be truncated. Second, a boolean value which will be true if the user should be prompted for the passphrase twice and the callback should verify that the two values supplied are equal. Third, the value given as the *userdata* parameter to :meth:`set_passwd_cb`. The *callback* must return a byte string. If an error occurs, *callback* should return a false value (e.g. an empty string). :param userdata: (optional) A Python object which will be given as argument to the callback :return: None """ if not callable(callback): raise TypeError("callback must be callable") self._passphrase_helper = self._wrap_callback(callback) self._passphrase_callback = self._passphrase_helper.callback _lib.SSL_CTX_set_default_passwd_cb( self._context, self._passphrase_callback ) self._passphrase_userdata = userdata def set_default_verify_paths(self) -> None: """ Specify that the platform provided CA certificates are to be used for verification purposes. This method has some caveats related to the binary wheels that cryptography (pyOpenSSL's primary dependency) ships: * macOS will only load certificates using this method if the user has the ``openssl@1.1`` `Homebrew `_ formula installed in the default location. * Windows will not work. * manylinux cryptography wheels will work on most common Linux distributions in pyOpenSSL 17.1.0 and above. pyOpenSSL detects the manylinux wheel and attempts to load roots via a fallback path. :return: None """ # SSL_CTX_set_default_verify_paths will attempt to load certs from # both a cafile and capath that are set at compile time. However, # it will first check environment variables and, if present, load # those paths instead set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) _openssl_assert(set_result == 1) # After attempting to set default_verify_paths we need to know whether # to go down the fallback path. # First we'll check to see if any env vars have been set. If so, # we won't try to do anything else because the user has set the path # themselves. dir_env_var = _ffi.string(_lib.X509_get_default_cert_dir_env()).decode( "ascii" ) file_env_var = _ffi.string( _lib.X509_get_default_cert_file_env() ).decode("ascii") if not self._check_env_vars_set(dir_env_var, file_env_var): default_dir = _ffi.string(_lib.X509_get_default_cert_dir()) default_file = _ffi.string(_lib.X509_get_default_cert_file()) # Now we check to see if the default_dir and default_file are set # to the exact values we use in our manylinux builds. If they are # then we know to load the fallbacks if ( default_dir == _CRYPTOGRAPHY_MANYLINUX_CA_DIR and default_file == _CRYPTOGRAPHY_MANYLINUX_CA_FILE ): # This is manylinux, let's load our fallback paths self._fallback_default_verify_paths( _CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS ) def _check_env_vars_set(self, dir_env_var: str, file_env_var: str) -> bool: """ Check to see if the default cert dir/file environment vars are present. :return: bool """ return ( os.environ.get(file_env_var) is not None or os.environ.get(dir_env_var) is not None ) def _fallback_default_verify_paths( self, file_path: List[str], dir_path: List[str] ) -> None: """ Default verify paths are based on the compiled version of OpenSSL. However, when pyca/cryptography is compiled as a manylinux wheel that compiled location can potentially be wrong. So, like Go, we will try a predefined set of paths and attempt to load roots from there. :return: None """ for cafile in file_path: if os.path.isfile(cafile): self.load_verify_locations(cafile) break for capath in dir_path: if os.path.isdir(capath): self.load_verify_locations(None, capath) break def use_certificate_chain_file(self, certfile: _StrOrBytesPath) -> None: """ Load a certificate chain from a file. :param certfile: The name of the certificate chain file (``bytes`` or ``str``). Must be PEM encoded. :return: None """ certfile = _path_bytes(certfile) result = _lib.SSL_CTX_use_certificate_chain_file( self._context, certfile ) if not result: _raise_current_error() def use_certificate_file( self, certfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM ) -> None: """ Load a certificate from a file :param certfile: The name of the certificate file (``bytes`` or ``str``). :param filetype: (optional) The encoding of the file, which is either :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is :const:`FILETYPE_PEM`. :return: None """ certfile = _path_bytes(certfile) if not isinstance(filetype, int): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_certificate_file( self._context, certfile, filetype ) if not use_result: _raise_current_error() def use_certificate(self, cert: X509) -> None: """ Load a certificate from a X509 object :param cert: The X509 object :return: None """ # Mirrored at Connection.use_certificate if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) if not use_result: _raise_current_error() def add_extra_chain_cert(self, certobj: X509) -> None: """ Add certificate to chain :param certobj: The X509 certificate object to add to the chain :return: None """ if not isinstance(certobj, X509): raise TypeError("certobj must be an X509 instance") copy = _lib.X509_dup(certobj._x509) add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) if not add_result: # TODO: This is untested. _lib.X509_free(copy) _raise_current_error() def _raise_passphrase_exception(self) -> None: if self._passphrase_helper is not None: self._passphrase_helper.raise_if_problem(Error) _raise_current_error() def use_privatekey_file( self, keyfile: _StrOrBytesPath, filetype: int = FILETYPE_PEM ) -> None: """ Load a private key from a file :param keyfile: The name of the key file (``bytes`` or ``str``) :param filetype: (optional) The encoding of the file, which is either :const:`FILETYPE_PEM` or :const:`FILETYPE_ASN1`. The default is :const:`FILETYPE_PEM`. :return: None """ keyfile = _path_bytes(keyfile) if not isinstance(filetype, int): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_PrivateKey_file( self._context, keyfile, filetype ) if not use_result: self._raise_passphrase_exception() def use_privatekey(self, pkey: PKey) -> None: """ Load a private key from a PKey object :param pkey: The PKey object :return: None """ # Mirrored at Connection.use_privatekey if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) if not use_result: self._raise_passphrase_exception() def check_privatekey(self) -> None: """ Check if the private key (loaded with :meth:`use_privatekey`) matches the certificate (loaded with :meth:`use_certificate`) :return: :data:`None` (raises :exc:`Error` if something's wrong) """ if not _lib.SSL_CTX_check_private_key(self._context): _raise_current_error() def load_client_ca(self, cafile: bytes) -> None: """ Load the trusted certificates that will be sent to the client. Does not actually imply any of the certificates are trusted; that must be configured separately. :param bytes cafile: The path to a certificates file in PEM format. :return: None """ ca_list = _lib.SSL_load_client_CA_file( _text_to_bytes_and_warn("cafile", cafile) ) _openssl_assert(ca_list != _ffi.NULL) _lib.SSL_CTX_set_client_CA_list(self._context, ca_list) def set_session_id(self, buf: bytes) -> None: """ Set the session id to *buf* within which a session can be reused for this Context object. This is needed when doing session resumption, because there is no way for a stored session to know which Context object it is associated with. :param bytes buf: The session id. :returns: None """ buf = _text_to_bytes_and_warn("buf", buf) _openssl_assert( _lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) == 1 ) def set_session_cache_mode(self, mode: int) -> None: """ Set the behavior of the session cache used by all connections using this Context. The previously set mode is returned. See :const:`SESS_CACHE_*` for details about particular modes. :param mode: One or more of the SESS_CACHE_* flags (combine using bitwise or) :returns: The previously set caching mode. .. versionadded:: 0.14 """ if not isinstance(mode, int): raise TypeError("mode must be an integer") return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) def get_session_cache_mode(self) -> int: """ Get the current session cache mode. :returns: The currently used cache mode. .. versionadded:: 0.14 """ return _lib.SSL_CTX_get_session_cache_mode(self._context) def set_verify( self, mode: int, callback: Optional[_VerifyCallback] = None ) -> None: """ Set the verification flags for this Context object to *mode* and specify that *callback* should be used for verification callbacks. :param mode: The verify mode, this should be one of :const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If :const:`VERIFY_PEER` is used, *mode* can be OR:ed with :const:`VERIFY_FAIL_IF_NO_PEER_CERT` and :const:`VERIFY_CLIENT_ONCE` to further control the behaviour. :param callback: The optional Python verification callback to use. This should take five arguments: A Connection object, an X509 object, and three integer variables, which are in turn potential error number, error depth and return code. *callback* should return True if verification passes and False otherwise. If omitted, OpenSSL's default verification is used. :return: None See SSL_CTX_set_verify(3SSL) for further details. """ if not isinstance(mode, int): raise TypeError("mode must be an integer") if callback is None: self._verify_helper = None self._verify_callback = None _lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) else: if not callable(callback): raise TypeError("callback must be callable") self._verify_helper = _VerifyHelper(callback) self._verify_callback = self._verify_helper.callback _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) def set_verify_depth(self, depth: int) -> None: """ Set the maximum depth for the certificate chain verification that shall be allowed for this Context object. :param depth: An integer specifying the verify depth :return: None """ if not isinstance(depth, int): raise TypeError("depth must be an integer") _lib.SSL_CTX_set_verify_depth(self._context, depth) def get_verify_mode(self) -> int: """ Retrieve the Context object's verify mode, as set by :meth:`set_verify`. :return: The verify mode """ return _lib.SSL_CTX_get_verify_mode(self._context) def get_verify_depth(self) -> int: """ Retrieve the Context object's verify depth, as set by :meth:`set_verify_depth`. :return: The verify depth """ return _lib.SSL_CTX_get_verify_depth(self._context) def load_tmp_dh(self, dhfile: _StrOrBytesPath) -> None: """ Load parameters for Ephemeral Diffie-Hellman :param dhfile: The file to load EDH parameters from (``bytes`` or ``str``). :return: None """ dhfile = _path_bytes(dhfile) bio = _lib.BIO_new_file(dhfile, b"r") if bio == _ffi.NULL: _raise_current_error() bio = _ffi.gc(bio, _lib.BIO_free) dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) dh = _ffi.gc(dh, _lib.DH_free) res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) _openssl_assert(res == 1) def set_tmp_ecdh(self, curve: _EllipticCurve) -> None: """ Select a curve to use for ECDHE key exchange. :param curve: A curve object to use as returned by either :meth:`OpenSSL.crypto.get_elliptic_curve` or :meth:`OpenSSL.crypto.get_elliptic_curves`. :return: None """ _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) def set_cipher_list(self, cipher_list: bytes) -> None: """ Set the list of ciphers to be used in this context. See the OpenSSL manual for more information (e.g. :manpage:`ciphers(1)`). :param bytes cipher_list: An OpenSSL cipher string. :return: None """ cipher_list = _text_to_bytes_and_warn("cipher_list", cipher_list) if not isinstance(cipher_list, bytes): raise TypeError("cipher_list must be a byte string.") _openssl_assert( _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) == 1 ) # In OpenSSL 1.1.1 setting the cipher list will always return TLS 1.3 # ciphers even if you pass an invalid cipher. Applications (like # Twisted) have tests that depend on an error being raised if an # invalid cipher string is passed, but without the following check # for the TLS 1.3 specific cipher suites it would never error. tmpconn = Connection(self, None) if tmpconn.get_cipher_list() == [ "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_GCM_SHA256", ]: raise Error( [ ( "SSL routines", "SSL_CTX_set_cipher_list", "no cipher match", ), ], ) def set_client_ca_list( self, certificate_authorities: Sequence[X509Name] ) -> None: """ Set the list of preferred client certificate signers for this server context. This list of certificate authorities will be sent to the client when the server requests a client certificate. :param certificate_authorities: a sequence of X509Names. :return: None .. versionadded:: 0.10 """ name_stack = _lib.sk_X509_NAME_new_null() _openssl_assert(name_stack != _ffi.NULL) try: for ca_name in certificate_authorities: if not isinstance(ca_name, X509Name): raise TypeError( f"client CAs must be X509Name objects, not " f"{type(ca_name).__name__} objects" ) copy = _lib.X509_NAME_dup(ca_name._name) _openssl_assert(copy != _ffi.NULL) push_result = _lib.sk_X509_NAME_push(name_stack, copy) if not push_result: _lib.X509_NAME_free(copy) _raise_current_error() except Exception: _lib.sk_X509_NAME_free(name_stack) raise _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) def add_client_ca(self, certificate_authority: X509) -> None: """ Add the CA certificate to the list of preferred signers for this context. The list of certificate authorities will be sent to the client when the server requests a client certificate. :param certificate_authority: certificate authority's X509 certificate. :return: None .. versionadded:: 0.10 """ if not isinstance(certificate_authority, X509): raise TypeError("certificate_authority must be an X509 instance") add_result = _lib.SSL_CTX_add_client_CA( self._context, certificate_authority._x509 ) _openssl_assert(add_result == 1) def set_timeout(self, timeout: int) -> None: """ Set the timeout for newly created sessions for this Context object to *timeout*. The default value is 300 seconds. See the OpenSSL manual for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`). :param timeout: The timeout in (whole) seconds :return: The previous session timeout """ if not isinstance(timeout, int): raise TypeError("timeout must be an integer") return _lib.SSL_CTX_set_timeout(self._context, timeout) def get_timeout(self) -> int: """ Retrieve session timeout, as set by :meth:`set_timeout`. The default is 300 seconds. :return: The session timeout """ return _lib.SSL_CTX_get_timeout(self._context) def set_info_callback( self, callback: Callable[["Connection", int, int], None] ) -> None: """ Set the information callback to *callback*. This function will be called from time to time during SSL handshakes. :param callback: The Python callback to use. This should take three arguments: a Connection object and two integers. The first integer specifies where in the SSL handshake the function was called, and the other the return code from a (possibly failed) internal function call. :return: None """ @wraps(callback) def wrapper(ssl, where, return_code): # type: ignore[no-untyped-def] callback(Connection._reverse_mapping[ssl], where, return_code) self._info_callback = _ffi.callback( "void (*)(const SSL *, int, int)", wrapper ) _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) @_requires_keylog def set_keylog_callback( self, callback: Callable[["Connection", bytes], None] ) -> None: """ Set the TLS key logging callback to *callback*. This function will be called whenever TLS key material is generated or received, in order to allow applications to store this keying material for debugging purposes. :param callback: The Python callback to use. This should take two arguments: a Connection object and a bytestring that contains the key material in the format used by NSS for its SSLKEYLOGFILE debugging output. :return: None """ @wraps(callback) def wrapper(ssl, line): # type: ignore[no-untyped-def] line = _ffi.string(line) callback(Connection._reverse_mapping[ssl], line) self._keylog_callback = _ffi.callback( "void (*)(const SSL *, const char *)", wrapper ) _lib.SSL_CTX_set_keylog_callback(self._context, self._keylog_callback) def get_app_data(self) -> Any: """ Get the application data (supplied via :meth:`set_app_data()`) :return: The application data """ return self._app_data def set_app_data(self, data: Any) -> None: """ Set the application data (will be returned from get_app_data()) :param data: Any Python object :return: None """ self._app_data = data def get_cert_store(self) -> Optional[X509Store]: """ Get the certificate store for the context. This can be used to add "trusted" certificates without using the :meth:`load_verify_locations` method. :return: A X509Store object or None if it does not have one. """ store = _lib.SSL_CTX_get_cert_store(self._context) if store == _ffi.NULL: # TODO: This is untested. return None pystore = X509Store.__new__(X509Store) pystore._store = store return pystore def set_options(self, options: int) -> None: """ Add options. Options set before are not cleared! This method should be used with the :const:`OP_*` constants. :param options: The options to add. :return: The new option bitmask. """ if not isinstance(options, int): raise TypeError("options must be an integer") return _lib.SSL_CTX_set_options(self._context, options) def set_mode(self, mode: int) -> None: """ Add modes via bitmask. Modes set before are not cleared! This method should be used with the :const:`MODE_*` constants. :param mode: The mode to add. :return: The new mode bitmask. """ if not isinstance(mode, int): raise TypeError("mode must be an integer") return _lib.SSL_CTX_set_mode(self._context, mode) def set_tlsext_servername_callback( self, callback: Callable[["Connection"], None] ) -> None: """ Specify a callback function to be called when clients specify a server name. :param callback: The callback function. It will be invoked with one argument, the Connection instance. .. versionadded:: 0.13 """ @wraps(callback) def wrapper(ssl, alert, arg): # type: ignore[no-untyped-def] callback(Connection._reverse_mapping[ssl]) return 0 self._tlsext_servername_callback = _ffi.callback( "int (*)(SSL *, int *, void *)", wrapper ) _lib.SSL_CTX_set_tlsext_servername_callback( self._context, self._tlsext_servername_callback ) def set_tlsext_use_srtp(self, profiles: bytes) -> None: """ Enable support for negotiating SRTP keying material. :param bytes profiles: A colon delimited list of protection profile names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``. :return: None """ if not isinstance(profiles, bytes): raise TypeError("profiles must be a byte string.") _openssl_assert( _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 ) @_requires_alpn def set_alpn_protos(self, protos: List[bytes]) -> None: """ Specify the protocols that the client is prepared to speak after the TLS connection has been negotiated using Application Layer Protocol Negotiation. :param protos: A list of the protocols to be offered to the server. This list should be a Python list of bytestrings representing the protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. """ # Different versions of OpenSSL are inconsistent about how they handle # empty proto lists (see #1043), so we avoid the problem entirely by # rejecting them ourselves. if not protos: raise ValueError("at least one protocol must be specified") # Take the list of protocols and join them together, prefixing them # with their lengths. protostr = b"".join( chain.from_iterable((bytes((len(p),)), p) for p in protos) ) # Build a C string from the list. We don't need to save this off # because OpenSSL immediately copies the data out. input_str = _ffi.new("unsigned char[]", protostr) # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() # return 0 on success, and non-0 on failure. # WARNING: these functions reverse the return value convention. _openssl_assert( _lib.SSL_CTX_set_alpn_protos( self._context, input_str, len(protostr) ) == 0 ) @_requires_alpn def set_alpn_select_callback(self, callback: _ALPNSelectCallback) -> None: """ Specify a callback function that will be called on the server when a client offers protocols using ALPN. :param callback: The callback function. It will be invoked with two arguments: the Connection, and a list of offered protocols as bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It can return one of those bytestrings to indicate the chosen protocol, the empty bytestring to terminate the TLS connection, or the :py:obj:`NO_OVERLAPPING_PROTOCOLS` to indicate that no offered protocol was selected, but that the connection should not be aborted. """ self._alpn_select_helper = _ALPNSelectHelper(callback) self._alpn_select_callback = self._alpn_select_helper.callback _lib.SSL_CTX_set_alpn_select_cb( self._context, self._alpn_select_callback, _ffi.NULL ) def _set_ocsp_callback( self, helper: typing.Union[ _OCSPClientCallbackHelper, _OCSPServerCallbackHelper ], data: Optional[Any], ) -> None: """ This internal helper does the common work for ``set_ocsp_server_callback`` and ``set_ocsp_client_callback``, which is almost all of it. """ self._ocsp_helper = helper self._ocsp_callback = helper.callback if data is None: self._ocsp_data = _ffi.NULL else: self._ocsp_data = _ffi.new_handle(data) rc = _lib.SSL_CTX_set_tlsext_status_cb( self._context, self._ocsp_callback ) _openssl_assert(rc == 1) rc = _lib.SSL_CTX_set_tlsext_status_arg(self._context, self._ocsp_data) _openssl_assert(rc == 1) def set_ocsp_server_callback( self, callback: _OCSPServerCallback[_T], data: Optional[_T] = None, ) -> None: """ Set a callback to provide OCSP data to be stapled to the TLS handshake on the server side. :param callback: The callback function. It will be invoked with two arguments: the Connection, and the optional arbitrary data you have provided. The callback must return a bytestring that contains the OCSP data to staple to the handshake. If no OCSP data is available for this connection, return the empty bytestring. :param data: Some opaque data that will be passed into the callback function when called. This can be used to avoid needing to do complex data lookups or to keep track of what context is being used. This parameter is optional. """ helper = _OCSPServerCallbackHelper(callback) self._set_ocsp_callback(helper, data) def set_ocsp_client_callback( self, callback: _OCSPClientCallback[_T], data: Optional[_T] = None, ) -> None: """ Set a callback to validate OCSP data stapled to the TLS handshake on the client side. :param callback: The callback function. It will be invoked with three arguments: the Connection, a bytestring containing the stapled OCSP assertion, and the optional arbitrary data you have provided. The callback must return a boolean that indicates the result of validating the OCSP data: ``True`` if the OCSP data is valid and the certificate can be trusted, or ``False`` if either the OCSP data is invalid or the certificate has been revoked. :param data: Some opaque data that will be passed into the callback function when called. This can be used to avoid needing to do complex data lookups or to keep track of what context is being used. This parameter is optional. """ helper = _OCSPClientCallbackHelper(callback) self._set_ocsp_callback(helper, data) def set_cookie_generate_callback( self, callback: _CookieGenerateCallback ) -> None: self._cookie_generate_helper = _CookieGenerateCallbackHelper(callback) _lib.SSL_CTX_set_cookie_generate_cb( self._context, self._cookie_generate_helper.callback, ) def set_cookie_verify_callback( self, callback: _CookieVerifyCallback ) -> None: self._cookie_verify_helper = _CookieVerifyCallbackHelper(callback) _lib.SSL_CTX_set_cookie_verify_cb( self._context, self._cookie_verify_helper.callback, ) class Connection: _reverse_mapping: typing.MutableMapping[Any, "Connection"] = ( WeakValueDictionary() ) def __init__( self, context: Context, socket: Optional[socket.socket] = None ) -> None: """ Create a new Connection object, using the given OpenSSL.SSL.Context instance and socket. :param context: An SSL Context to use for this connection :param socket: The socket to use for transport layer """ if not isinstance(context, Context): raise TypeError("context must be a Context instance") ssl = _lib.SSL_new(context._context) self._ssl = _ffi.gc(ssl, _lib.SSL_free) # We set SSL_MODE_AUTO_RETRY to handle situations where OpenSSL returns # an SSL_ERROR_WANT_READ when processing a non-application data packet # even though there is still data on the underlying transport. # See https://github.com/openssl/openssl/issues/6234 for more details. _lib.SSL_set_mode(self._ssl, _lib.SSL_MODE_AUTO_RETRY) self._context = context self._app_data = None # References to strings used for Application Layer Protocol # Negotiation. These strings get copied at some point but it's well # after the callback returns, so we have to hang them somewhere to # avoid them getting freed. self._alpn_select_callback_args = None # Reference the verify_callback of the Context. This ensures that if # set_verify is called again after the SSL object has been created we # do not point to a dangling reference self._verify_helper = context._verify_helper self._verify_callback = context._verify_callback # And likewise for the cookie callbacks self._cookie_generate_helper = context._cookie_generate_helper self._cookie_verify_helper = context._cookie_verify_helper self._reverse_mapping[self._ssl] = self if socket is None: self._socket = None # Don't set up any gc for these, SSL_free will take care of them. self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) _openssl_assert(self._into_ssl != _ffi.NULL) self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) _openssl_assert(self._from_ssl != _ffi.NULL) _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) else: self._into_ssl = None self._from_ssl = None self._socket = socket set_result = _lib.SSL_set_fd( self._ssl, _asFileDescriptor(self._socket) ) _openssl_assert(set_result == 1) def __getattr__(self, name: str) -> Any: """ Look up attributes on the wrapped socket object if they are not found on the Connection object. """ if self._socket is None: raise AttributeError( f"'{self.__class__.__name__}' object has no attribute '{name}'" ) else: return getattr(self._socket, name) def _raise_ssl_error(self, ssl: Any, result: int) -> None: if self._context._verify_helper is not None: self._context._verify_helper.raise_if_problem() if self._context._alpn_select_helper is not None: self._context._alpn_select_helper.raise_if_problem() if self._context._ocsp_helper is not None: self._context._ocsp_helper.raise_if_problem() error = _lib.SSL_get_error(ssl, result) if error == _lib.SSL_ERROR_WANT_READ: raise WantReadError() elif error == _lib.SSL_ERROR_WANT_WRITE: raise WantWriteError() elif error == _lib.SSL_ERROR_ZERO_RETURN: raise ZeroReturnError() elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: # TODO: This is untested. raise WantX509LookupError() elif error == _lib.SSL_ERROR_SYSCALL: if _lib.ERR_peek_error() == 0: if result < 0: if platform == "win32": errno = _ffi.getwinerror()[0] else: errno = _ffi.errno if errno != 0: raise SysCallError(errno, errorcode.get(errno)) raise SysCallError(-1, "Unexpected EOF") else: # TODO: This is untested. _raise_current_error() elif error == _lib.SSL_ERROR_SSL and _lib.ERR_peek_error() != 0: # In 3.0.x an unexpected EOF no longer triggers syscall error # but we want to maintain compatibility so we check here and # raise syscall if it is an EOF. Since we're not actually sure # what else could raise SSL_ERROR_SSL we check for the presence # of the OpenSSL 3 constant SSL_R_UNEXPECTED_EOF_WHILE_READING # and if it's not present we just raise an error, which matches # the behavior before we added this elif section peeked_error = _lib.ERR_peek_error() reason = _lib.ERR_GET_REASON(peeked_error) if _lib.Cryptography_HAS_UNEXPECTED_EOF_WHILE_READING: _openssl_assert( reason == _lib.SSL_R_UNEXPECTED_EOF_WHILE_READING ) _lib.ERR_clear_error() raise SysCallError(-1, "Unexpected EOF") else: _raise_current_error() elif error == _lib.SSL_ERROR_NONE: pass else: _raise_current_error() def get_context(self) -> Context: """ Retrieve the :class:`Context` object associated with this :class:`Connection`. """ return self._context def set_context(self, context: Context) -> None: """ Switch this connection to a new session context. :param context: A :class:`Context` instance giving the new session context to use. """ if not isinstance(context, Context): raise TypeError("context must be a Context instance") _lib.SSL_set_SSL_CTX(self._ssl, context._context) self._context = context def get_servername(self) -> Optional[bytes]: """ Retrieve the servername extension value if provided in the client hello message, or None if there wasn't one. :return: A byte string giving the server name or :data:`None`. .. versionadded:: 0.13 """ name = _lib.SSL_get_servername( self._ssl, _lib.TLSEXT_NAMETYPE_host_name ) if name == _ffi.NULL: return None return _ffi.string(name) def set_verify( self, mode: int, callback: Optional[_VerifyCallback] = None ) -> None: """ Override the Context object's verification flags for this specific connection. See :py:meth:`Context.set_verify` for details. """ if not isinstance(mode, int): raise TypeError("mode must be an integer") if callback is None: self._verify_helper = None self._verify_callback = None _lib.SSL_set_verify(self._ssl, mode, _ffi.NULL) else: if not callable(callback): raise TypeError("callback must be callable") self._verify_helper = _VerifyHelper(callback) self._verify_callback = self._verify_helper.callback _lib.SSL_set_verify(self._ssl, mode, self._verify_callback) def get_verify_mode(self) -> int: """ Retrieve the Connection object's verify mode, as set by :meth:`set_verify`. :return: The verify mode """ return _lib.SSL_get_verify_mode(self._ssl) def use_certificate(self, cert: X509) -> None: """ Load a certificate from a X509 object :param cert: The X509 object :return: None """ # Mirrored from Context.use_certificate if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") use_result = _lib.SSL_use_certificate(self._ssl, cert._x509) if not use_result: _raise_current_error() def use_privatekey(self, pkey: PKey) -> None: """ Load a private key from a PKey object :param pkey: The PKey object :return: None """ # Mirrored from Context.use_privatekey if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") use_result = _lib.SSL_use_PrivateKey(self._ssl, pkey._pkey) if not use_result: self._context._raise_passphrase_exception() def set_ciphertext_mtu(self, mtu: int) -> None: """ For DTLS, set the maximum UDP payload size (*not* including IP/UDP overhead). Note that you might have to set :data:`OP_NO_QUERY_MTU` to prevent OpenSSL from spontaneously clearing this. :param mtu: An integer giving the maximum transmission unit. .. versionadded:: 21.1 """ _lib.SSL_set_mtu(self._ssl, mtu) def get_cleartext_mtu(self) -> int: """ For DTLS, get the maximum size of unencrypted data you can pass to :meth:`write` without exceeding the MTU (as passed to :meth:`set_ciphertext_mtu`). :return: The effective MTU as an integer. .. versionadded:: 21.1 """ if not hasattr(_lib, "DTLS_get_data_mtu"): raise NotImplementedError("requires OpenSSL 1.1.1 or better") return _lib.DTLS_get_data_mtu(self._ssl) def set_tlsext_host_name(self, name: bytes) -> None: """ Set the value of the servername extension to send in the client hello. :param name: A byte string giving the name. .. versionadded:: 0.13 """ if not isinstance(name, bytes): raise TypeError("name must be a byte string") elif b"\0" in name: raise TypeError("name must not contain NUL byte") # XXX I guess this can fail sometimes? _lib.SSL_set_tlsext_host_name(self._ssl, name) def pending(self) -> int: """ Get the number of bytes that can be safely read from the SSL buffer (**not** the underlying transport buffer). :return: The number of bytes available in the receive buffer. """ return _lib.SSL_pending(self._ssl) def send(self, buf: bytes, flags: int = 0) -> int: """ Send data on the connection. NOTE: If you get one of the WantRead, WantWrite or WantX509Lookup exceptions on this, you have to call the method again with the SAME buffer. :param buf: The string, buffer or memoryview to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ # Backward compatibility buf = _text_to_bytes_and_warn("buf", buf) with _ffi.from_buffer(buf) as data: # check len(buf) instead of len(data) for testability if len(buf) > 2147483647: raise ValueError( "Cannot send more than 2**31-1 bytes at once." ) result = _lib.SSL_write(self._ssl, data, len(data)) self._raise_ssl_error(self._ssl, result) return result write = send def sendall(self, buf: bytes, flags: int = 0) -> int: """ Send "all" data on the connection. This calls send() repeatedly until all data is sent. If an error occurs, it's impossible to tell how much data has been sent. :param buf: The string, buffer or memoryview to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ buf = _text_to_bytes_and_warn("buf", buf) with _ffi.from_buffer(buf) as data: left_to_send = len(buf) total_sent = 0 while left_to_send: # SSL_write's num arg is an int, # so we cannot send more than 2**31-1 bytes at once. result = _lib.SSL_write( self._ssl, data + total_sent, min(left_to_send, 2147483647) ) self._raise_ssl_error(self._ssl, result) total_sent += result left_to_send -= result return total_sent def recv(self, bufsiz: int, flags: Optional[int] = None) -> bytes: """ Receive data on the connection. :param bufsiz: The maximum number of bytes to read :param flags: (optional) The only supported flag is ``MSG_PEEK``, all other flags are ignored. :return: The string read from the Connection """ buf = _no_zero_allocator("char[]", bufsiz) if flags is not None and flags & socket.MSG_PEEK: result = _lib.SSL_peek(self._ssl, buf, bufsiz) else: result = _lib.SSL_read(self._ssl, buf, bufsiz) self._raise_ssl_error(self._ssl, result) return _ffi.buffer(buf, result)[:] read = recv def recv_into( self, buffer: Any, # collections.abc.Buffer once we use Python 3.12+ nbytes: Optional[int] = None, flags: Optional[int] = None, ) -> int: """ Receive data on the connection and copy it directly into the provided buffer, rather than creating a new string. :param buffer: The buffer to copy into. :param nbytes: (optional) The maximum number of bytes to read into the buffer. If not present, defaults to the size of the buffer. If larger than the size of the buffer, is reduced to the size of the buffer. :param flags: (optional) The only supported flag is ``MSG_PEEK``, all other flags are ignored. :return: The number of bytes read into the buffer. """ if nbytes is None: nbytes = len(buffer) else: nbytes = min(nbytes, len(buffer)) # We need to create a temporary buffer. This is annoying, it would be # better if we could pass memoryviews straight into the SSL_read call, # but right now we can't. Revisit this if CFFI gets that ability. buf = _no_zero_allocator("char[]", nbytes) if flags is not None and flags & socket.MSG_PEEK: result = _lib.SSL_peek(self._ssl, buf, nbytes) else: result = _lib.SSL_read(self._ssl, buf, nbytes) self._raise_ssl_error(self._ssl, result) # This strange line is all to avoid a memory copy. The buffer protocol # should allow us to assign a CFFI buffer to the LHS of this line, but # on CPython 3.3+ that segfaults. As a workaround, we can temporarily # wrap it in a memoryview. buffer[:result] = memoryview(_ffi.buffer(buf, result)) return result def _handle_bio_errors(self, bio: Any, result: int) -> typing.NoReturn: if _lib.BIO_should_retry(bio): if _lib.BIO_should_read(bio): raise WantReadError() elif _lib.BIO_should_write(bio): # TODO: This is untested. raise WantWriteError() elif _lib.BIO_should_io_special(bio): # TODO: This is untested. I think io_special means the socket # BIO has a not-yet connected socket. raise ValueError("BIO_should_io_special") else: # TODO: This is untested. raise ValueError("unknown bio failure") else: # TODO: This is untested. _raise_current_error() def bio_read(self, bufsiz: int) -> bytes: """ If the Connection was created with a memory BIO, this method can be used to read bytes from the write end of that memory BIO. Many Connection methods will add bytes which must be read in this manner or the buffer will eventually fill up and the Connection will be able to take no further actions. :param bufsiz: The maximum number of bytes to read :return: The string read. """ if self._from_ssl is None: raise TypeError("Connection sock was not None") if not isinstance(bufsiz, int): raise TypeError("bufsiz must be an integer") buf = _no_zero_allocator("char[]", bufsiz) result = _lib.BIO_read(self._from_ssl, buf, bufsiz) if result <= 0: self._handle_bio_errors(self._from_ssl, result) return _ffi.buffer(buf, result)[:] def bio_write(self, buf: bytes) -> int: """ If the Connection was created with a memory BIO, this method can be used to add bytes to the read end of that memory BIO. The Connection can then read the bytes (for example, in response to a call to :meth:`recv`). :param buf: The string to put into the memory BIO. :return: The number of bytes written """ buf = _text_to_bytes_and_warn("buf", buf) if self._into_ssl is None: raise TypeError("Connection sock was not None") with _ffi.from_buffer(buf) as data: result = _lib.BIO_write(self._into_ssl, data, len(data)) if result <= 0: self._handle_bio_errors(self._into_ssl, result) return result def renegotiate(self) -> bool: """ Renegotiate the session. :return: True if the renegotiation can be started, False otherwise """ if not self.renegotiate_pending(): _openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1) return True return False def do_handshake(self) -> None: """ Perform an SSL handshake (usually called after :meth:`renegotiate` or one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can raise the same exceptions as :meth:`send` and :meth:`recv`. :return: None. """ result = _lib.SSL_do_handshake(self._ssl) self._raise_ssl_error(self._ssl, result) def renegotiate_pending(self) -> bool: """ Check if there's a renegotiation in progress, it will return False once a renegotiation is finished. :return: Whether there's a renegotiation in progress """ return _lib.SSL_renegotiate_pending(self._ssl) == 1 def total_renegotiations(self) -> int: """ Find out the total number of renegotiations. :return: The number of renegotiations. """ return _lib.SSL_total_renegotiations(self._ssl) def connect(self, addr: Any) -> None: """ Call the :meth:`connect` method of the underlying socket and set up SSL on the socket, using the :class:`Context` object supplied to this :class:`Connection` object at creation. :param addr: A remote address :return: What the socket's connect method returns """ _lib.SSL_set_connect_state(self._ssl) return self._socket.connect(addr) # type: ignore[return-value, union-attr] def connect_ex(self, addr: Any) -> int: """ Call the :meth:`connect_ex` method of the underlying socket and set up SSL on the socket, using the Context object supplied to this Connection object at creation. Note that if the :meth:`connect_ex` method of the socket doesn't return 0, SSL won't be initialized. :param addr: A remove address :return: What the socket's connect_ex method returns """ connect_ex = self._socket.connect_ex # type: ignore[union-attr] self.set_connect_state() return connect_ex(addr) def accept(self) -> Tuple["Connection", Any]: """ Call the :meth:`accept` method of the underlying socket and set up SSL on the returned socket, using the Context object supplied to this :class:`Connection` object at creation. :return: A *(conn, addr)* pair where *conn* is the new :class:`Connection` object created, and *address* is as returned by the socket's :meth:`accept`. """ client, addr = self._socket.accept() # type: ignore[union-attr] conn = Connection(self._context, client) conn.set_accept_state() return (conn, addr) def DTLSv1_listen(self) -> None: """ Call the OpenSSL function DTLSv1_listen on this connection. See the OpenSSL manual for more details. :return: None """ # Possible future extension: return the BIO_ADDR in some form. bio_addr = _lib.BIO_ADDR_new() try: result = _lib.DTLSv1_listen(self._ssl, bio_addr) finally: _lib.BIO_ADDR_free(bio_addr) # DTLSv1_listen is weird. A zero return value means 'didn't find a # ClientHello with valid cookie, but keep trying'. So basically # WantReadError. But it doesn't work correctly with _raise_ssl_error. # So we raise it manually instead. if self._cookie_generate_helper is not None: self._cookie_generate_helper.raise_if_problem() if self._cookie_verify_helper is not None: self._cookie_verify_helper.raise_if_problem() if result == 0: raise WantReadError() if result < 0: self._raise_ssl_error(self._ssl, result) def DTLSv1_get_timeout(self) -> Optional[int]: """ Determine when the DTLS SSL object next needs to perform internal processing due to the passage of time. When the returned number of seconds have passed, the :meth:`DTLSv1_handle_timeout` method needs to be called. :return: The time left in seconds before the next timeout or `None` if no timeout is currently active. """ ptv_sec = _ffi.new("time_t *") ptv_usec = _ffi.new("long *") if _lib.Cryptography_DTLSv1_get_timeout(self._ssl, ptv_sec, ptv_usec): return ptv_sec[0] + (ptv_usec[0] / 1000000) else: return None def DTLSv1_handle_timeout(self) -> bool: """ Handles any timeout events which have become pending on a DTLS SSL object. :return: `True` if there was a pending timeout, `False` otherwise. """ result = _lib.DTLSv1_handle_timeout(self._ssl) if result < 0: self._raise_ssl_error(self._ssl, result) assert False, "unreachable" else: return bool(result) def bio_shutdown(self) -> None: """ If the Connection was created with a memory BIO, this method can be used to indicate that *end of file* has been reached on the read end of that memory BIO. :return: None """ if self._from_ssl is None: raise TypeError("Connection sock was not None") _lib.BIO_set_mem_eof_return(self._into_ssl, 0) def shutdown(self) -> bool: """ Send the shutdown message to the Connection. :return: True if the shutdown completed successfully (i.e. both sides have sent closure alerts), False otherwise (in which case you call :meth:`recv` or :meth:`send` when the connection becomes readable/writeable). """ result = _lib.SSL_shutdown(self._ssl) if result < 0: self._raise_ssl_error(self._ssl, result) assert False, "unreachable" elif result > 0: return True else: return False def get_cipher_list(self) -> List[str]: """ Retrieve the list of ciphers used by the Connection object. :return: A list of native cipher strings. """ ciphers = [] for i in count(): result = _lib.SSL_get_cipher_list(self._ssl, i) if result == _ffi.NULL: break ciphers.append(_ffi.string(result).decode("utf-8")) return ciphers def get_client_ca_list(self) -> List[X509Name]: """ Get CAs whose certificates are suggested for client authentication. :return: If this is a server connection, the list of certificate authorities that will be sent or has been sent to the client, as controlled by this :class:`Connection`'s :class:`Context`. If this is a client connection, the list will be empty until the connection with the server is established. .. versionadded:: 0.10 """ ca_names = _lib.SSL_get_client_CA_list(self._ssl) if ca_names == _ffi.NULL: # TODO: This is untested. return [] result = [] for i in range(_lib.sk_X509_NAME_num(ca_names)): name = _lib.sk_X509_NAME_value(ca_names, i) copy = _lib.X509_NAME_dup(name) _openssl_assert(copy != _ffi.NULL) pyname = X509Name.__new__(X509Name) pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) result.append(pyname) return result def makefile(self, *args: Any, **kwargs: Any) -> typing.NoReturn: """ The makefile() method is not implemented, since there is no dup semantics for SSL connections :raise: NotImplementedError """ raise NotImplementedError( "Cannot make file object of OpenSSL.SSL.Connection" ) def get_app_data(self) -> Any: """ Retrieve application data as set by :meth:`set_app_data`. :return: The application data """ return self._app_data def set_app_data(self, data: Any) -> None: """ Set application data :param data: The application data :return: None """ self._app_data = data def get_shutdown(self) -> int: """ Get the shutdown state of the Connection. :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. """ return _lib.SSL_get_shutdown(self._ssl) def set_shutdown(self, state: int) -> None: """ Set the shutdown state of the Connection. :param state: bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. :return: None """ if not isinstance(state, int): raise TypeError("state must be an integer") _lib.SSL_set_shutdown(self._ssl, state) def get_state_string(self) -> bytes: """ Retrieve a verbose string detailing the state of the Connection. :return: A string representing the state """ return _ffi.string(_lib.SSL_state_string_long(self._ssl)) def server_random(self) -> Optional[bytes]: """ Retrieve the random value used with the server hello message. :return: A string representing the state """ session = _lib.SSL_get_session(self._ssl) if session == _ffi.NULL: return None length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_server_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] def client_random(self) -> Optional[bytes]: """ Retrieve the random value used with the client hello message. :return: A string representing the state """ session = _lib.SSL_get_session(self._ssl) if session == _ffi.NULL: return None length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_client_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] def master_key(self) -> Optional[bytes]: """ Retrieve the value of the master key for this session. :return: A string representing the state """ session = _lib.SSL_get_session(self._ssl) if session == _ffi.NULL: return None length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_SESSION_get_master_key(session, outp, length) return _ffi.buffer(outp, length)[:] def export_keying_material( self, label: bytes, olen: int, context: Optional[bytes] = None ) -> bytes: """ Obtain keying material for application use. :param: label - a disambiguating label string as described in RFC 5705 :param: olen - the length of the exported key material in bytes :param: context - a per-association context value :return: the exported key material bytes or None """ outp = _no_zero_allocator("unsigned char[]", olen) context_buf = _ffi.NULL context_len = 0 use_context = 0 if context is not None: context_buf = context context_len = len(context) use_context = 1 success = _lib.SSL_export_keying_material( self._ssl, outp, olen, label, len(label), context_buf, context_len, use_context, ) _openssl_assert(success == 1) return _ffi.buffer(outp, olen)[:] def sock_shutdown(self, *args: Any, **kwargs: Any) -> None: """ Call the :meth:`shutdown` method of the underlying socket. See :manpage:`shutdown(2)`. :return: What the socket's shutdown() method returns """ return self._socket.shutdown(*args, **kwargs) # type: ignore[return-value, union-attr] def get_certificate(self) -> Optional[X509]: """ Retrieve the local certificate (if any) :return: The local certificate """ cert = _lib.SSL_get_certificate(self._ssl) if cert != _ffi.NULL: _lib.X509_up_ref(cert) return X509._from_raw_x509_ptr(cert) return None def get_peer_certificate(self) -> Optional[X509]: """ Retrieve the other side's certificate (if any) :return: The peer's certificate """ cert = _lib.SSL_get_peer_certificate(self._ssl) if cert != _ffi.NULL: return X509._from_raw_x509_ptr(cert) return None @staticmethod def _cert_stack_to_list(cert_stack: Any) -> List[X509]: """ Internal helper to convert a STACK_OF(X509) to a list of X509 instances. """ result = [] for i in range(_lib.sk_X509_num(cert_stack)): cert = _lib.sk_X509_value(cert_stack, i) _openssl_assert(cert != _ffi.NULL) res = _lib.X509_up_ref(cert) _openssl_assert(res >= 1) pycert = X509._from_raw_x509_ptr(cert) result.append(pycert) return result def get_peer_cert_chain(self) -> Optional[List[X509]]: """ Retrieve the other side's certificate (if any) :return: A list of X509 instances giving the peer's certificate chain, or None if it does not have one. """ cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) if cert_stack == _ffi.NULL: return None return self._cert_stack_to_list(cert_stack) def get_verified_chain(self) -> Optional[List[X509]]: """ Retrieve the verified certificate chain of the peer including the peer's end entity certificate. It must be called after a session has been successfully established. If peer verification was not successful the chain may be incomplete, invalid, or None. :return: A list of X509 instances giving the peer's verified certificate chain, or None if it does not have one. .. versionadded:: 20.0 """ # OpenSSL 1.1+ cert_stack = _lib.SSL_get0_verified_chain(self._ssl) if cert_stack == _ffi.NULL: return None return self._cert_stack_to_list(cert_stack) def want_read(self) -> bool: """ Checks if more data has to be read from the transport layer to complete an operation. :return: True iff more data has to be read """ return _lib.SSL_want_read(self._ssl) def want_write(self) -> bool: """ Checks if there is data to write to the transport layer to complete an operation. :return: True iff there is data to write """ return _lib.SSL_want_write(self._ssl) def set_accept_state(self) -> None: """ Set the connection to work in server mode. The handshake will be handled automatically by read/write. :return: None """ _lib.SSL_set_accept_state(self._ssl) def set_connect_state(self) -> None: """ Set the connection to work in client mode. The handshake will be handled automatically by read/write. :return: None """ _lib.SSL_set_connect_state(self._ssl) def get_session(self) -> Optional[Session]: """ Returns the Session currently used. :return: An instance of :class:`OpenSSL.SSL.Session` or :obj:`None` if no session exists. .. versionadded:: 0.14 """ session = _lib.SSL_get1_session(self._ssl) if session == _ffi.NULL: return None pysession = Session.__new__(Session) pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) return pysession def set_session(self, session: Session) -> None: """ Set the session to be used when the TLS/SSL connection is established. :param session: A Session instance representing the session to use. :returns: None .. versionadded:: 0.14 """ if not isinstance(session, Session): raise TypeError("session must be a Session instance") result = _lib.SSL_set_session(self._ssl, session._session) _openssl_assert(result == 1) def _get_finished_message( self, function: Callable[[Any, Any, int], int] ) -> Optional[bytes]: """ Helper to implement :meth:`get_finished` and :meth:`get_peer_finished`. :param function: Either :data:`SSL_get_finished`: or :data:`SSL_get_peer_finished`. :return: :data:`None` if the desired message has not yet been received, otherwise the contents of the message. """ # The OpenSSL documentation says nothing about what might happen if the # count argument given is zero. Specifically, it doesn't say whether # the output buffer may be NULL in that case or not. Inspection of the # implementation reveals that it calls memcpy() unconditionally. # Section 7.1.4, paragraph 1 of the C standard suggests that # memcpy(NULL, source, 0) is not guaranteed to produce defined (let # alone desirable) behavior (though it probably does on just about # every implementation...) # # Allocate a tiny buffer to pass in (instead of just passing NULL as # one might expect) for the initial call so as to be safe against this # potentially undefined behavior. empty = _ffi.new("char[]", 0) size = function(self._ssl, empty, 0) if size == 0: # No Finished message so far. return None buf = _no_zero_allocator("char[]", size) function(self._ssl, buf, size) return _ffi.buffer(buf, size)[:] def get_finished(self) -> Optional[bytes]: """ Obtain the latest TLS Finished message that we sent. :return: The contents of the message or :obj:`None` if the TLS handshake has not yet completed. .. versionadded:: 0.15 """ return self._get_finished_message(_lib.SSL_get_finished) def get_peer_finished(self) -> Optional[bytes]: """ Obtain the latest TLS Finished message that we received from the peer. :return: The contents of the message or :obj:`None` if the TLS handshake has not yet completed. .. versionadded:: 0.15 """ return self._get_finished_message(_lib.SSL_get_peer_finished) def get_cipher_name(self) -> Optional[str]: """ Obtain the name of the currently used cipher. :returns: The name of the currently used cipher or :obj:`None` if no connection has been established. .. versionadded:: 0.15 """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) return name.decode("utf-8") def get_cipher_bits(self) -> Optional[int]: """ Obtain the number of secret bits of the currently used cipher. :returns: The number of secret bits of the currently used cipher or :obj:`None` if no connection has been established. .. versionadded:: 0.15 """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) def get_cipher_version(self) -> Optional[str]: """ Obtain the protocol version of the currently used cipher. :returns: The protocol name of the currently used cipher or :obj:`None` if no connection has been established. .. versionadded:: 0.15 """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: version = _ffi.string(_lib.SSL_CIPHER_get_version(cipher)) return version.decode("utf-8") def get_protocol_version_name(self) -> str: """ Retrieve the protocol version of the current connection. :returns: The TLS version of the current connection, for example the value for TLS 1.2 would be ``TLSv1.2``or ``Unknown`` for connections that were not successfully established. """ version = _ffi.string(_lib.SSL_get_version(self._ssl)) return version.decode("utf-8") def get_protocol_version(self) -> int: """ Retrieve the SSL or TLS protocol version of the current connection. :returns: The TLS version of the current connection. For example, it will return ``0x769`` for connections made over TLS version 1. """ version = _lib.SSL_version(self._ssl) return version @_requires_alpn def set_alpn_protos(self, protos: List[bytes]) -> None: """ Specify the client's ALPN protocol list. These protocols are offered to the server during protocol negotiation. :param protos: A list of the protocols to be offered to the server. This list should be a Python list of bytestrings representing the protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. """ # Different versions of OpenSSL are inconsistent about how they handle # empty proto lists (see #1043), so we avoid the problem entirely by # rejecting them ourselves. if not protos: raise ValueError("at least one protocol must be specified") # Take the list of protocols and join them together, prefixing them # with their lengths. protostr = b"".join( chain.from_iterable((bytes((len(p),)), p) for p in protos) ) # Build a C string from the list. We don't need to save this off # because OpenSSL immediately copies the data out. input_str = _ffi.new("unsigned char[]", protostr) # https://www.openssl.org/docs/man1.1.0/man3/SSL_CTX_set_alpn_protos.html: # SSL_CTX_set_alpn_protos() and SSL_set_alpn_protos() # return 0 on success, and non-0 on failure. # WARNING: these functions reverse the return value convention. _openssl_assert( _lib.SSL_set_alpn_protos(self._ssl, input_str, len(protostr)) == 0 ) @_requires_alpn def get_alpn_proto_negotiated(self) -> bytes: """ Get the protocol that was negotiated by ALPN. :returns: A bytestring of the protocol name. If no protocol has been negotiated yet, returns an empty bytestring. """ data = _ffi.new("unsigned char **") data_len = _ffi.new("unsigned int *") _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) if not data_len: return b"" return _ffi.buffer(data[0], data_len[0])[:] def get_selected_srtp_profile(self) -> bytes: """ Get the SRTP protocol which was negotiated. :returns: A bytestring of the SRTP profile name. If no profile has been negotiated yet, returns an empty bytestring. """ profile = _lib.SSL_get_selected_srtp_profile(self._ssl) if not profile: return b"" return _ffi.string(profile.name) def request_ocsp(self) -> None: """ Called to request that the server sends stapled OCSP data, if available. If this is not called on the client side then the server will not send OCSP data. Should be used in conjunction with :meth:`Context.set_ocsp_client_callback`. """ rc = _lib.SSL_set_tlsext_status_type( self._ssl, _lib.TLSEXT_STATUSTYPE_ocsp ) _openssl_assert(rc == 1) pyopenssl-24.2.1/src/OpenSSL/__init__.py000066400000000000000000000007611465571700000200300ustar00rootroot00000000000000# Copyright (C) AB Strakt # See LICENSE for details. """ pyOpenSSL - A simple wrapper around the OpenSSL library """ from OpenSSL import SSL, crypto from OpenSSL.version import ( __author__, __copyright__, __email__, __license__, __summary__, __title__, __uri__, __version__, ) __all__ = [ "SSL", "crypto", "__author__", "__copyright__", "__email__", "__license__", "__summary__", "__title__", "__uri__", "__version__", ] pyopenssl-24.2.1/src/OpenSSL/_util.py000066400000000000000000000067231465571700000174110ustar00rootroot00000000000000import os import sys import warnings from typing import Any, Callable, NoReturn, Type, Union from cryptography.hazmat.bindings.openssl.binding import Binding StrOrBytesPath = Union[str, bytes, os.PathLike] binding = Binding() ffi = binding.ffi lib = binding.lib # This is a special CFFI allocator that does not bother to zero its memory # after allocation. This has vastly better performance on large allocations and # so should be used whenever we don't need the memory zeroed out. no_zero_allocator = ffi.new_allocator(should_clear_after_alloc=False) def text(charp: Any) -> str: """ Get a native string type representing of the given CFFI ``char*`` object. :param charp: A C-style string represented using CFFI. :return: :class:`str` """ if not charp: return "" return ffi.string(charp).decode("utf-8") def exception_from_error_queue(exception_type: Type[Exception]) -> NoReturn: """ Convert an OpenSSL library failure into a Python exception. When a call to the native OpenSSL library fails, this is usually signalled by the return value, and an error code is stored in an error queue associated with the current thread. The err library provides functions to obtain these error codes and textual error messages. """ errors = [] while True: error = lib.ERR_get_error() if error == 0: break errors.append( ( text(lib.ERR_lib_error_string(error)), text(lib.ERR_func_error_string(error)), text(lib.ERR_reason_error_string(error)), ) ) raise exception_type(errors) def make_assert(error: Type[Exception]) -> Callable[[bool], Any]: """ Create an assert function that uses :func:`exception_from_error_queue` to raise an exception wrapped by *error*. """ def openssl_assert(ok: bool) -> None: """ If *ok* is not True, retrieve the error from OpenSSL and raise it. """ if ok is not True: exception_from_error_queue(error) return openssl_assert def path_bytes(s: StrOrBytesPath) -> bytes: """ Convert a Python path to a :py:class:`bytes` for the path which can be passed into an OpenSSL API accepting a filename. :param s: A path (valid for os.fspath). :return: An instance of :py:class:`bytes`. """ b = os.fspath(s) if isinstance(b, str): return b.encode(sys.getfilesystemencoding()) else: return b def byte_string(s: str) -> bytes: return s.encode("charmap") # A marker object to observe whether some optional arguments are passed any # value or not. UNSPECIFIED = object() _TEXT_WARNING = "str for {0} is no longer accepted, use bytes" def text_to_bytes_and_warn(label: str, obj: Any) -> Any: """ If ``obj`` is text, emit a warning that it should be bytes instead and try to convert it to bytes automatically. :param str label: The name of the parameter from which ``obj`` was taken (so a developer can easily find the source of the problem and correct it). :return: If ``obj`` is the text string type, a ``bytes`` object giving the UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is returned. """ if isinstance(obj, str): warnings.warn( _TEXT_WARNING.format(label), category=DeprecationWarning, stacklevel=3, ) return obj.encode("utf-8") return obj pyopenssl-24.2.1/src/OpenSSL/crypto.py000066400000000000000000003003531465571700000176110ustar00rootroot00000000000000import calendar import datetime import functools import typing from base64 import b16encode from functools import partial from os import PathLike from typing import ( Any, Callable, Iterable, List, NoReturn, Optional, Sequence, Set, Tuple, Type, Union, ) from cryptography import utils, x509 from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, rsa, ) from OpenSSL._util import ( UNSPECIFIED as _UNSPECIFIED, ) from OpenSSL._util import ( byte_string as _byte_string, ) from OpenSSL._util import ( exception_from_error_queue as _exception_from_error_queue, ) from OpenSSL._util import ( ffi as _ffi, ) from OpenSSL._util import ( lib as _lib, ) from OpenSSL._util import ( make_assert as _make_assert, ) from OpenSSL._util import ( path_bytes as _path_bytes, ) from OpenSSL._util import ( text_to_bytes_and_warn as _text_to_bytes_and_warn, ) __all__ = [ "FILETYPE_PEM", "FILETYPE_ASN1", "FILETYPE_TEXT", "TYPE_RSA", "TYPE_DSA", "Error", "PKey", "get_elliptic_curves", "get_elliptic_curve", "X509Name", "X509Extension", "X509Req", "X509", "X509StoreFlags", "X509Store", "X509StoreContextError", "X509StoreContext", "load_certificate", "dump_certificate", "dump_publickey", "dump_privatekey", "Revoked", "CRL", "load_publickey", "load_privatekey", "dump_certificate_request", "load_certificate_request", "sign", "verify", "dump_crl", "load_crl", ] _Key = Union[ dsa.DSAPrivateKey, dsa.DSAPublicKey, ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey, ed448.Ed448PrivateKey, ed448.Ed448PublicKey, rsa.RSAPrivateKey, rsa.RSAPublicKey, ] StrOrBytesPath = Union[str, bytes, PathLike] PassphraseCallableT = Union[bytes, Callable[..., bytes]] FILETYPE_PEM: int = _lib.SSL_FILETYPE_PEM FILETYPE_ASN1: int = _lib.SSL_FILETYPE_ASN1 # TODO This was an API mistake. OpenSSL has no such constant. FILETYPE_TEXT = 2**16 - 1 TYPE_RSA: int = _lib.EVP_PKEY_RSA TYPE_DSA: int = _lib.EVP_PKEY_DSA TYPE_DH: int = _lib.EVP_PKEY_DH TYPE_EC: int = _lib.EVP_PKEY_EC class Error(Exception): """ An error occurred in an `OpenSSL.crypto` API. """ _raise_current_error = partial(_exception_from_error_queue, Error) _openssl_assert = _make_assert(Error) def _untested_error(where: str) -> NoReturn: """ An OpenSSL API failed somehow. Additionally, the failure which was encountered isn't one that's exercised by the test suite so future behavior of pyOpenSSL is now somewhat less predictable. """ raise RuntimeError(f"Unknown {where} failure") def _new_mem_buf(buffer: Optional[bytes] = None) -> Any: """ Allocate a new OpenSSL memory BIO. Arrange for the garbage collector to clean it up automatically. :param buffer: None or some bytes to use to put into the BIO so that they can be read out. """ if buffer is None: bio = _lib.BIO_new(_lib.BIO_s_mem()) free = _lib.BIO_free else: data = _ffi.new("char[]", buffer) bio = _lib.BIO_new_mem_buf(data, len(buffer)) # Keep the memory alive as long as the bio is alive! def free(bio: Any, ref: Any = data) -> Any: return _lib.BIO_free(bio) _openssl_assert(bio != _ffi.NULL) bio = _ffi.gc(bio, free) return bio def _bio_to_string(bio: Any) -> bytes: """ Copy the contents of an OpenSSL BIO object into a Python byte string. """ result_buffer = _ffi.new("char**") buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) return _ffi.buffer(result_buffer[0], buffer_length)[:] def _set_asn1_time(boundary: Any, when: bytes) -> None: """ The the time value of an ASN1 time object. @param boundary: An ASN1_TIME pointer (or an object safely castable to that type) which will have its value set. @param when: A string representation of the desired time value. @raise TypeError: If C{when} is not a L{bytes} string. @raise ValueError: If C{when} does not represent a time in the required format. @raise RuntimeError: If the time value cannot be set for some other (unspecified) reason. """ if not isinstance(when, bytes): raise TypeError("when must be a byte string") # ASN1_TIME_set_string validates the string without writing anything # when the destination is NULL. _openssl_assert(boundary != _ffi.NULL) set_result = _lib.ASN1_TIME_set_string(boundary, when) if set_result == 0: raise ValueError("Invalid string") def _new_asn1_time(when: bytes) -> Any: """ Behaves like _set_asn1_time but returns a new ASN1_TIME object. @param when: A string representation of the desired time value. @raise TypeError: If C{when} is not a L{bytes} string. @raise ValueError: If C{when} does not represent a time in the required format. @raise RuntimeError: If the time value cannot be set for some other (unspecified) reason. """ ret = _lib.ASN1_TIME_new() _openssl_assert(ret != _ffi.NULL) ret = _ffi.gc(ret, _lib.ASN1_TIME_free) _set_asn1_time(ret, when) return ret def _get_asn1_time(timestamp: Any) -> Optional[bytes]: """ Retrieve the time value of an ASN1 time object. @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to that type) from which the time value will be retrieved. @return: The time value from C{timestamp} as a L{bytes} string in a certain format. Or C{None} if the object contains no time value. """ string_timestamp = _ffi.cast("ASN1_STRING*", timestamp) if _lib.ASN1_STRING_length(string_timestamp) == 0: return None elif ( _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME ): return _ffi.string(_lib.ASN1_STRING_get0_data(string_timestamp)) else: generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) if generalized_timestamp[0] == _ffi.NULL: # This may happen: # - if timestamp was not an ASN1_TIME # - if allocating memory for the ASN1_GENERALIZEDTIME failed # - if a copy of the time data from timestamp cannot be made for # the newly allocated ASN1_GENERALIZEDTIME # # These are difficult to test. cffi enforces the ASN1_TIME type. # Memory allocation failures are a pain to trigger # deterministically. _untested_error("ASN1_TIME_to_generalizedtime") else: string_timestamp = _ffi.cast( "ASN1_STRING*", generalized_timestamp[0] ) string_data = _lib.ASN1_STRING_get0_data(string_timestamp) string_result = _ffi.string(string_data) _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) return string_result class _X509NameInvalidator: def __init__(self) -> None: self._names: List[X509Name] = [] def add(self, name: "X509Name") -> None: self._names.append(name) def clear(self) -> None: for name in self._names: # Breaks the object, but also prevents UAF! del name._name class PKey: """ A class representing an DSA or RSA public key or key pair. """ _only_public = False _initialized = True def __init__(self) -> None: pkey = _lib.EVP_PKEY_new() self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) self._initialized = False def to_cryptography_key(self) -> _Key: """ Export as a ``cryptography`` key. :rtype: One of ``cryptography``'s `key interfaces`_. .. _key interfaces: https://cryptography.io/en/latest/hazmat/\ primitives/asymmetric/rsa/#key-interfaces .. versionadded:: 16.1.0 """ from cryptography.hazmat.primitives.serialization import ( load_der_private_key, load_der_public_key, ) if self._only_public: der = dump_publickey(FILETYPE_ASN1, self) return load_der_public_key(der) else: der = dump_privatekey(FILETYPE_ASN1, self) return load_der_private_key(der, None) @classmethod def from_cryptography_key(cls, crypto_key: _Key) -> "PKey": """ Construct based on a ``cryptography`` *crypto_key*. :param crypto_key: A ``cryptography`` key. :type crypto_key: One of ``cryptography``'s `key interfaces`_. :rtype: PKey .. versionadded:: 16.1.0 """ if not isinstance( crypto_key, ( dsa.DSAPrivateKey, dsa.DSAPublicKey, ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PrivateKey, ed25519.Ed25519PublicKey, ed448.Ed448PrivateKey, ed448.Ed448PublicKey, rsa.RSAPrivateKey, rsa.RSAPublicKey, ), ): raise TypeError("Unsupported key type") from cryptography.hazmat.primitives.serialization import ( Encoding, NoEncryption, PrivateFormat, PublicFormat, ) if isinstance( crypto_key, ( dsa.DSAPublicKey, ec.EllipticCurvePublicKey, ed25519.Ed25519PublicKey, ed448.Ed448PublicKey, rsa.RSAPublicKey, ), ): return load_publickey( FILETYPE_ASN1, crypto_key.public_bytes( Encoding.DER, PublicFormat.SubjectPublicKeyInfo ), ) else: der = crypto_key.private_bytes( Encoding.DER, PrivateFormat.PKCS8, NoEncryption() ) return load_privatekey(FILETYPE_ASN1, der) def generate_key(self, type: int, bits: int) -> None: """ Generate a key pair of the given type, with the given number of bits. This generates a key "into" the this object. :param type: The key type. :type type: :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` :param bits: The number of bits. :type bits: :py:data:`int` ``>= 0`` :raises TypeError: If :py:data:`type` or :py:data:`bits` isn't of the appropriate type. :raises ValueError: If the number of bits isn't an integer of the appropriate size. :return: ``None`` """ if not isinstance(type, int): raise TypeError("type must be an integer") if not isinstance(bits, int): raise TypeError("bits must be an integer") if type == TYPE_RSA: if bits <= 0: raise ValueError("Invalid number of bits") # TODO Check error return exponent = _lib.BN_new() exponent = _ffi.gc(exponent, _lib.BN_free) _lib.BN_set_word(exponent, _lib.RSA_F4) rsa = _lib.RSA_new() result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) _openssl_assert(result == 1) result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) _openssl_assert(result == 1) elif type == TYPE_DSA: dsa = _lib.DSA_new() _openssl_assert(dsa != _ffi.NULL) dsa = _ffi.gc(dsa, _lib.DSA_free) res = _lib.DSA_generate_parameters_ex( dsa, bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL ) _openssl_assert(res == 1) _openssl_assert(_lib.DSA_generate_key(dsa) == 1) _openssl_assert(_lib.EVP_PKEY_set1_DSA(self._pkey, dsa) == 1) else: raise Error("No such key type") self._initialized = True def check(self) -> bool: """ Check the consistency of an RSA private key. This is the Python equivalent of OpenSSL's ``RSA_check_key``. :return: ``True`` if key is consistent. :raise OpenSSL.crypto.Error: if the key is inconsistent. :raise TypeError: if the key is of a type which cannot be checked. Only RSA keys can currently be checked. """ if self._only_public: raise TypeError("public key only") if _lib.EVP_PKEY_type(self.type()) != _lib.EVP_PKEY_RSA: raise TypeError("Only RSA keys can currently be checked.") rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) rsa = _ffi.gc(rsa, _lib.RSA_free) result = _lib.RSA_check_key(rsa) if result == 1: return True _raise_current_error() def type(self) -> int: """ Returns the type of the key :return: The type of the key. """ return _lib.EVP_PKEY_id(self._pkey) def bits(self) -> int: """ Returns the number of bits of the key :return: The number of bits of the key. """ return _lib.EVP_PKEY_bits(self._pkey) class _EllipticCurve: """ A representation of a supported elliptic curve. @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` instances each of which represents one curve supported by the system. @type _curves: :py:type:`NoneType` or :py:type:`set` """ _curves = None def __ne__(self, other: Any) -> bool: """ Implement cooperation with the right-hand side argument of ``!=``. Python 3 seems to have dropped this cooperation in this very narrow circumstance. """ if isinstance(other, _EllipticCurve): return super().__ne__(other) return NotImplemented @classmethod def _load_elliptic_curves(cls, lib: Any) -> Set["_EllipticCurve"]: """ Get the curves supported by OpenSSL. :param lib: The OpenSSL library binding object. :return: A :py:type:`set` of ``cls`` instances giving the names of the elliptic curves the underlying library supports. """ num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) builtin_curves = _ffi.new("EC_builtin_curve[]", num_curves) # The return value on this call should be num_curves again. We # could check it to make sure but if it *isn't* then.. what could # we do? Abort the whole process, I suppose...? -exarkun lib.EC_get_builtin_curves(builtin_curves, num_curves) return set(cls.from_nid(lib, c.nid) for c in builtin_curves) @classmethod def _get_elliptic_curves(cls, lib: Any) -> Set["_EllipticCurve"]: """ Get, cache, and return the curves supported by OpenSSL. :param lib: The OpenSSL library binding object. :return: A :py:type:`set` of ``cls`` instances giving the names of the elliptic curves the underlying library supports. """ if cls._curves is None: cls._curves = cls._load_elliptic_curves(lib) return cls._curves @classmethod def from_nid(cls, lib: Any, nid: int) -> "_EllipticCurve": """ Instantiate a new :py:class:`_EllipticCurve` associated with the given OpenSSL NID. :param lib: The OpenSSL library binding object. :param nid: The OpenSSL NID the resulting curve object will represent. This must be a curve NID (and not, for example, a hash NID) or subsequent operations will fail in unpredictable ways. :type nid: :py:class:`int` :return: The curve object. """ return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) def __init__(self, lib: Any, nid: int, name: str) -> None: """ :param _lib: The :py:mod:`cryptography` binding instance used to interface with OpenSSL. :param _nid: The OpenSSL NID identifying the curve this object represents. :type _nid: :py:class:`int` :param name: The OpenSSL short name identifying the curve this object represents. :type name: :py:class:`unicode` """ self._lib = lib self._nid = nid self.name = name def __repr__(self) -> str: return f"" def _to_EC_KEY(self) -> Any: """ Create a new OpenSSL EC_KEY structure initialized to use this curve. The structure is automatically garbage collected when the Python object is garbage collected. """ key = self._lib.EC_KEY_new_by_curve_name(self._nid) return _ffi.gc(key, _lib.EC_KEY_free) def get_elliptic_curves() -> Set["_EllipticCurve"]: """ Return a set of objects representing the elliptic curves supported in the OpenSSL build in use. The curve objects have a :py:class:`unicode` ``name`` attribute by which they identify themselves. The curve objects are useful as values for the argument accepted by :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be used for ECDHE key exchange. """ return _EllipticCurve._get_elliptic_curves(_lib) def get_elliptic_curve(name: str) -> _EllipticCurve: """ Return a single curve object selected by name. See :py:func:`get_elliptic_curves` for information about curve objects. :param name: The OpenSSL short name identifying the curve object to retrieve. :type name: :py:class:`unicode` If the named curve is not supported then :py:class:`ValueError` is raised. """ for curve in get_elliptic_curves(): if curve.name == name: return curve raise ValueError("unknown curve name", name) @functools.total_ordering class X509Name: """ An X.509 Distinguished Name. :ivar countryName: The country of the entity. :ivar C: Alias for :py:attr:`countryName`. :ivar stateOrProvinceName: The state or province of the entity. :ivar ST: Alias for :py:attr:`stateOrProvinceName`. :ivar localityName: The locality of the entity. :ivar L: Alias for :py:attr:`localityName`. :ivar organizationName: The organization name of the entity. :ivar O: Alias for :py:attr:`organizationName`. :ivar organizationalUnitName: The organizational unit of the entity. :ivar OU: Alias for :py:attr:`organizationalUnitName` :ivar commonName: The common name of the entity. :ivar CN: Alias for :py:attr:`commonName`. :ivar emailAddress: The e-mail address of the entity. """ def __init__(self, name: "X509Name") -> None: """ Create a new X509Name, copying the given X509Name instance. :param name: The name to copy. :type name: :py:class:`X509Name` """ name = _lib.X509_NAME_dup(name._name) self._name: Any = _ffi.gc(name, _lib.X509_NAME_free) def __setattr__(self, name: str, value: Any) -> None: if name.startswith("_"): return super().__setattr__(name, value) # Note: we really do not want str subclasses here, so we do not use # isinstance. if type(name) is not str: raise TypeError( f"attribute name must be string, not " f"'{type(value).__name__:.200}'" ) nid = _lib.OBJ_txt2nid(_byte_string(name)) if nid == _lib.NID_undef: try: _raise_current_error() except Error: pass raise AttributeError("No such attribute") # If there's an old entry for this NID, remove it for i in range(_lib.X509_NAME_entry_count(self._name)): ent = _lib.X509_NAME_get_entry(self._name, i) ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) ent_nid = _lib.OBJ_obj2nid(ent_obj) if nid == ent_nid: ent = _lib.X509_NAME_delete_entry(self._name, i) _lib.X509_NAME_ENTRY_free(ent) break if isinstance(value, str): value = value.encode("utf-8") add_result = _lib.X509_NAME_add_entry_by_NID( self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0 ) if not add_result: _raise_current_error() def __getattr__(self, name: str) -> Optional[str]: """ Find attribute. An X509Name object has the following attributes: countryName (alias C), stateOrProvince (alias ST), locality (alias L), organization (alias O), organizationalUnit (alias OU), commonName (alias CN) and more... """ nid = _lib.OBJ_txt2nid(_byte_string(name)) if nid == _lib.NID_undef: # This is a bit weird. OBJ_txt2nid indicated failure, but it seems # a lower level function, a2d_ASN1_OBJECT, also feels the need to # push something onto the error queue. If we don't clean that up # now, someone else will bump into it later and be quite confused. # See lp#314814. try: _raise_current_error() except Error: pass raise AttributeError("No such attribute") entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) if entry_index == -1: return None entry = _lib.X509_NAME_get_entry(self._name, entry_index) data = _lib.X509_NAME_ENTRY_get_data(entry) result_buffer = _ffi.new("unsigned char**") data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) _openssl_assert(data_length >= 0) try: result = _ffi.buffer(result_buffer[0], data_length)[:].decode( "utf-8" ) finally: # XXX untested _lib.OPENSSL_free(result_buffer[0]) return result def __eq__(self, other: Any) -> bool: if not isinstance(other, X509Name): return NotImplemented return _lib.X509_NAME_cmp(self._name, other._name) == 0 def __lt__(self, other: Any) -> bool: if not isinstance(other, X509Name): return NotImplemented return _lib.X509_NAME_cmp(self._name, other._name) < 0 def __repr__(self) -> str: """ String representation of an X509Name """ result_buffer = _ffi.new("char[]", 512) format_result = _lib.X509_NAME_oneline( self._name, result_buffer, len(result_buffer) ) _openssl_assert(format_result != _ffi.NULL) return "".format( _ffi.string(result_buffer).decode("utf-8"), ) def hash(self) -> int: """ Return an integer representation of the first four bytes of the MD5 digest of the DER representation of the name. This is the Python equivalent of OpenSSL's ``X509_NAME_hash``. :return: The (integer) hash of this name. :rtype: :py:class:`int` """ return _lib.X509_NAME_hash(self._name) def der(self) -> bytes: """ Return the DER encoding of this name. :return: The DER encoded form of this name. :rtype: :py:class:`bytes` """ result_buffer = _ffi.new("unsigned char**") encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) _openssl_assert(encode_result >= 0) string_result = _ffi.buffer(result_buffer[0], encode_result)[:] _lib.OPENSSL_free(result_buffer[0]) return string_result def get_components(self) -> List[Tuple[bytes, bytes]]: """ Returns the components of this name, as a sequence of 2-tuples. :return: The components of this name. :rtype: :py:class:`list` of ``name, value`` tuples. """ result = [] for i in range(_lib.X509_NAME_entry_count(self._name)): ent = _lib.X509_NAME_get_entry(self._name, i) fname = _lib.X509_NAME_ENTRY_get_object(ent) fval = _lib.X509_NAME_ENTRY_get_data(ent) nid = _lib.OBJ_obj2nid(fname) name = _lib.OBJ_nid2sn(nid) # ffi.string does not handle strings containing NULL bytes # (which may have been generated by old, broken software) value = _ffi.buffer( _lib.ASN1_STRING_get0_data(fval), _lib.ASN1_STRING_length(fval) )[:] result.append((_ffi.string(name), value)) return result class X509Extension: """ An X.509 v3 certificate extension. """ def __init__( self, type_name: bytes, critical: bool, value: bytes, subject: Optional["X509"] = None, issuer: Optional["X509"] = None, ) -> None: """ Initializes an X509 extension. :param type_name: The name of the type of extension_ to create. :type type_name: :py:data:`bytes` :param bool critical: A flag indicating whether this is a critical extension. :param value: The OpenSSL textual representation of the extension's value. :type value: :py:data:`bytes` :param subject: Optional X509 certificate to use as subject. :type subject: :py:class:`X509` :param issuer: Optional X509 certificate to use as issuer. :type issuer: :py:class:`X509` .. _extension: https://www.openssl.org/docs/manmaster/man5/ x509v3_config.html#STANDARD-EXTENSIONS """ ctx = _ffi.new("X509V3_CTX*") # A context is necessary for any extension which uses the r2i # conversion method. That is, X509V3_EXT_nconf may segfault if passed # a NULL ctx. Start off by initializing most of the fields to NULL. _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) # We have no configuration database - but perhaps we should (some # extensions may require it). _lib.X509V3_set_ctx_nodb(ctx) # Initialize the subject and issuer, if appropriate. ctx is a local, # and as far as I can tell none of the X509V3_* APIs invoked here steal # any references, so no need to mess with reference counts or # duplicates. if issuer is not None: if not isinstance(issuer, X509): raise TypeError("issuer must be an X509 instance") ctx.issuer_cert = issuer._x509 if subject is not None: if not isinstance(subject, X509): raise TypeError("subject must be an X509 instance") ctx.subject_cert = subject._x509 if critical: # There are other OpenSSL APIs which would let us pass in critical # separately, but they're harder to use, and since value is already # a pile of crappy junk smuggling a ton of utterly important # structured data, what's the point of trying to avoid nasty stuff # with strings? (However, X509V3_EXT_i2d in particular seems like # it would be a better API to invoke. I do not know where to get # the ext_struc it desires for its last parameter, though.) value = b"critical," + value extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) if extension == _ffi.NULL: _raise_current_error() self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) @property def _nid(self) -> Any: return _lib.OBJ_obj2nid( _lib.X509_EXTENSION_get_object(self._extension) ) _prefixes: typing.ClassVar[typing.Dict[int, str]] = { _lib.GEN_EMAIL: "email", _lib.GEN_DNS: "DNS", _lib.GEN_URI: "URI", } def _subjectAltNameString(self) -> str: names = _ffi.cast( "GENERAL_NAMES*", _lib.X509V3_EXT_d2i(self._extension) ) names = _ffi.gc(names, _lib.GENERAL_NAMES_free) parts = [] for i in range(_lib.sk_GENERAL_NAME_num(names)): name = _lib.sk_GENERAL_NAME_value(names, i) try: label = self._prefixes[name.type] except KeyError: bio = _new_mem_buf() _lib.GENERAL_NAME_print(bio, name) parts.append(_bio_to_string(bio).decode("utf-8")) else: value = _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[ : ].decode("utf-8") parts.append(label + ":" + value) return ", ".join(parts) def __str__(self) -> str: """ :return: a nice text representation of the extension """ if _lib.NID_subject_alt_name == self._nid: return self._subjectAltNameString() bio = _new_mem_buf() print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0) _openssl_assert(print_result != 0) return _bio_to_string(bio).decode("utf-8") def get_critical(self) -> bool: """ Returns the critical field of this X.509 extension. :return: The critical field. """ return _lib.X509_EXTENSION_get_critical(self._extension) def get_short_name(self) -> bytes: """ Returns the short type name of this X.509 extension. The result is a byte string such as :py:const:`b"basicConstraints"`. :return: The short type name. :rtype: :py:data:`bytes` .. versionadded:: 0.12 """ obj = _lib.X509_EXTENSION_get_object(self._extension) nid = _lib.OBJ_obj2nid(obj) # OpenSSL 3.1.0 has a bug where nid2sn returns NULL for NIDs that # previously returned UNDEF. This is a workaround for that issue. # https://github.com/openssl/openssl/commit/908ba3ed9adbb3df90f76 buf = _lib.OBJ_nid2sn(nid) if buf != _ffi.NULL: return _ffi.string(buf) else: return b"UNDEF" def get_data(self) -> bytes: """ Returns the data of the X509 extension, encoded as ASN.1. :return: The ASN.1 encoded data of this X509 extension. :rtype: :py:data:`bytes` .. versionadded:: 0.12 """ octet_result = _lib.X509_EXTENSION_get_data(self._extension) string_result = _ffi.cast("ASN1_STRING*", octet_result) char_result = _lib.ASN1_STRING_get0_data(string_result) result_length = _lib.ASN1_STRING_length(string_result) return _ffi.buffer(char_result, result_length)[:] _X509ExtensionInternal = X509Extension utils.deprecated( X509Extension, __name__, ( "X509Extension support in pyOpenSSL is deprecated. You should use the " "APIs in cryptography." ), DeprecationWarning, name="X509Extension", ) class X509Req: """ An X.509 certificate signing requests. """ def __init__(self) -> None: req = _lib.X509_REQ_new() self._req = _ffi.gc(req, _lib.X509_REQ_free) # Default to version 0. self.set_version(0) def to_cryptography(self) -> x509.CertificateSigningRequest: """ Export as a ``cryptography`` certificate signing request. :rtype: ``cryptography.x509.CertificateSigningRequest`` .. versionadded:: 17.1.0 """ from cryptography.x509 import load_der_x509_csr der = _dump_certificate_request_internal(FILETYPE_ASN1, self) return load_der_x509_csr(der) @classmethod def from_cryptography( cls, crypto_req: x509.CertificateSigningRequest ) -> "X509Req": """ Construct based on a ``cryptography`` *crypto_req*. :param crypto_req: A ``cryptography`` X.509 certificate signing request :type crypto_req: ``cryptography.x509.CertificateSigningRequest`` :rtype: X509Req .. versionadded:: 17.1.0 """ if not isinstance(crypto_req, x509.CertificateSigningRequest): raise TypeError("Must be a certificate signing request") from cryptography.hazmat.primitives.serialization import Encoding der = crypto_req.public_bytes(Encoding.DER) return _load_certificate_request_internal(FILETYPE_ASN1, der) def set_pubkey(self, pkey: PKey) -> None: """ Set the public key of the certificate signing request. :param pkey: The public key to use. :type pkey: :py:class:`PKey` :return: ``None`` """ set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) _openssl_assert(set_result == 1) def get_pubkey(self) -> PKey: """ Get the public key of the certificate signing request. :return: The public key. :rtype: :py:class:`PKey` """ pkey = PKey.__new__(PKey) pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) _openssl_assert(pkey._pkey != _ffi.NULL) pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def set_version(self, version: int) -> None: """ Set the version subfield (RFC 2986, section 4.1) of the certificate request. :param int version: The version number. :return: ``None`` """ if not isinstance(version, int): raise TypeError("version must be an int") if version != 0: raise ValueError( "Invalid version. The only valid version for X509Req is 0." ) set_result = _lib.X509_REQ_set_version(self._req, version) _openssl_assert(set_result == 1) def get_version(self) -> int: """ Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate request. :return: The value of the version subfield. :rtype: :py:class:`int` """ return _lib.X509_REQ_get_version(self._req) def get_subject(self) -> X509Name: """ Return the subject of this certificate signing request. This creates a new :class:`X509Name` that wraps the underlying subject name field on the certificate signing request. Modifying it will modify the underlying signing request, and will have the effect of modifying any other :class:`X509Name` that refers to this subject. :return: The subject of this certificate signing request. :rtype: :class:`X509Name` """ name = X509Name.__new__(X509Name) name._name = _lib.X509_REQ_get_subject_name(self._req) _openssl_assert(name._name != _ffi.NULL) # The name is owned by the X509Req structure. As long as the X509Name # Python object is alive, keep the X509Req Python object alive. name._owner = self return name def add_extensions( self, extensions: Iterable[_X509ExtensionInternal] ) -> None: """ Add extensions to the certificate signing request. :param extensions: The X.509 extensions to add. :type extensions: iterable of :py:class:`X509Extension` :return: ``None`` """ stack = _lib.sk_X509_EXTENSION_new_null() _openssl_assert(stack != _ffi.NULL) stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free) for ext in extensions: if not isinstance(ext, _X509ExtensionInternal): raise ValueError("One of the elements is not an X509Extension") # TODO push can fail (here and elsewhere) _lib.sk_X509_EXTENSION_push(stack, ext._extension) add_result = _lib.X509_REQ_add_extensions(self._req, stack) _openssl_assert(add_result == 1) def get_extensions(self) -> List[_X509ExtensionInternal]: """ Get X.509 extensions in the certificate signing request. :return: The X.509 extensions in this request. :rtype: :py:class:`list` of :py:class:`X509Extension` objects. .. versionadded:: 0.15 """ exts = [] native_exts_obj = _lib.X509_REQ_get_extensions(self._req) native_exts_obj = _ffi.gc( native_exts_obj, lambda x: _lib.sk_X509_EXTENSION_pop_free( x, _ffi.addressof(_lib._original_lib, "X509_EXTENSION_free"), ), ) for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)): ext = _X509ExtensionInternal.__new__(_X509ExtensionInternal) extension = _lib.X509_EXTENSION_dup( _lib.sk_X509_EXTENSION_value(native_exts_obj, i) ) ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) exts.append(ext) return exts def sign(self, pkey: PKey, digest: str) -> None: """ Sign the certificate signing request with this key and digest type. :param pkey: The key pair to sign with. :type pkey: :py:class:`PKey` :param digest: The name of the message digest to use for the signature, e.g. :py:data:`"sha256"`. :type digest: :py:class:`str` :return: ``None`` """ if pkey._only_public: raise ValueError("Key has only public part") if not pkey._initialized: raise ValueError("Key is uninitialized") digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) _openssl_assert(sign_result > 0) def verify(self, pkey: PKey) -> bool: """ Verifies the signature on this certificate signing request. :param PKey key: A public key. :return: ``True`` if the signature is correct. :rtype: bool :raises OpenSSL.crypto.Error: If the signature is invalid or there is a problem verifying the signature. """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") result = _lib.X509_REQ_verify(self._req, pkey._pkey) if result <= 0: _raise_current_error() return result _X509ReqInternal = X509Req utils.deprecated( X509Req, __name__, ( "CSR support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="X509Req", ) class X509: """ An X.509 certificate. """ def __init__(self) -> None: x509 = _lib.X509_new() _openssl_assert(x509 != _ffi.NULL) self._x509 = _ffi.gc(x509, _lib.X509_free) self._issuer_invalidator = _X509NameInvalidator() self._subject_invalidator = _X509NameInvalidator() @classmethod def _from_raw_x509_ptr(cls, x509: Any) -> "X509": cert = cls.__new__(cls) cert._x509 = _ffi.gc(x509, _lib.X509_free) cert._issuer_invalidator = _X509NameInvalidator() cert._subject_invalidator = _X509NameInvalidator() return cert def to_cryptography(self) -> x509.Certificate: """ Export as a ``cryptography`` certificate. :rtype: ``cryptography.x509.Certificate`` .. versionadded:: 17.1.0 """ from cryptography.x509 import load_der_x509_certificate der = dump_certificate(FILETYPE_ASN1, self) return load_der_x509_certificate(der) @classmethod def from_cryptography(cls, crypto_cert: x509.Certificate) -> "X509": """ Construct based on a ``cryptography`` *crypto_cert*. :param crypto_key: A ``cryptography`` X.509 certificate. :type crypto_key: ``cryptography.x509.Certificate`` :rtype: X509 .. versionadded:: 17.1.0 """ if not isinstance(crypto_cert, x509.Certificate): raise TypeError("Must be a certificate") from cryptography.hazmat.primitives.serialization import Encoding der = crypto_cert.public_bytes(Encoding.DER) return load_certificate(FILETYPE_ASN1, der) def set_version(self, version: int) -> None: """ Set the version number of the certificate. Note that the version value is zero-based, eg. a value of 0 is V1. :param version: The version number of the certificate. :type version: :py:class:`int` :return: ``None`` """ if not isinstance(version, int): raise TypeError("version must be an integer") _openssl_assert(_lib.X509_set_version(self._x509, version) == 1) def get_version(self) -> int: """ Return the version number of the certificate. :return: The version number of the certificate. :rtype: :py:class:`int` """ return _lib.X509_get_version(self._x509) def get_pubkey(self) -> PKey: """ Get the public key of the certificate. :return: The public key. :rtype: :py:class:`PKey` """ pkey = PKey.__new__(PKey) pkey._pkey = _lib.X509_get_pubkey(self._x509) if pkey._pkey == _ffi.NULL: _raise_current_error() pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def set_pubkey(self, pkey: PKey) -> None: """ Set the public key of the certificate. :param pkey: The public key. :type pkey: :py:class:`PKey` :return: :py:data:`None` """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) _openssl_assert(set_result == 1) def sign(self, pkey: PKey, digest: str) -> None: """ Sign the certificate with this key and digest type. :param pkey: The key to sign with. :type pkey: :py:class:`PKey` :param digest: The name of the message digest to use. :type digest: :py:class:`str` :return: :py:data:`None` """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") if pkey._only_public: raise ValueError("Key only has public part") if not pkey._initialized: raise ValueError("Key is uninitialized") evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) if evp_md == _ffi.NULL: raise ValueError("No such digest method") sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) _openssl_assert(sign_result > 0) def get_signature_algorithm(self) -> bytes: """ Return the signature algorithm used in the certificate. :return: The name of the algorithm. :rtype: :py:class:`bytes` :raises ValueError: If the signature algorithm is undefined. .. versionadded:: 0.13 """ sig_alg = _lib.X509_get0_tbs_sigalg(self._x509) alg = _ffi.new("ASN1_OBJECT **") _lib.X509_ALGOR_get0(alg, _ffi.NULL, _ffi.NULL, sig_alg) nid = _lib.OBJ_obj2nid(alg[0]) if nid == _lib.NID_undef: raise ValueError("Undefined signature algorithm") return _ffi.string(_lib.OBJ_nid2ln(nid)) def digest(self, digest_name: str) -> bytes: """ Return the digest of the X509 object. :param digest_name: The name of the digest algorithm to use. :type digest_name: :py:class:`str` :return: The digest of the object, formatted as :py:const:`b":"`-delimited hex pairs. :rtype: :py:class:`bytes` """ digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) if digest == _ffi.NULL: raise ValueError("No such digest method") result_buffer = _ffi.new("unsigned char[]", _lib.EVP_MAX_MD_SIZE) result_length = _ffi.new("unsigned int[]", 1) result_length[0] = len(result_buffer) digest_result = _lib.X509_digest( self._x509, digest, result_buffer, result_length ) _openssl_assert(digest_result == 1) return b":".join( [ b16encode(ch).upper() for ch in _ffi.buffer(result_buffer, result_length[0]) ] ) def subject_name_hash(self) -> bytes: """ Return the hash of the X509 subject. :return: The hash of the subject. :rtype: :py:class:`bytes` """ return _lib.X509_subject_name_hash(self._x509) def set_serial_number(self, serial: int) -> None: """ Set the serial number of the certificate. :param serial: The new serial number. :type serial: :py:class:`int` :return: :py:data`None` """ if not isinstance(serial, int): raise TypeError("serial must be an integer") hex_serial = hex(serial)[2:] hex_serial_bytes = hex_serial.encode("ascii") bignum_serial = _ffi.new("BIGNUM**") # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like # it. If bignum is still NULL after this call, then the return value # is actually the result. I hope. -exarkun small_serial = _lib.BN_hex2bn(bignum_serial, hex_serial_bytes) if bignum_serial[0] == _ffi.NULL: set_result = _lib.ASN1_INTEGER_set( _lib.X509_get_serialNumber(self._x509), small_serial ) if set_result: # TODO Not tested _raise_current_error() else: asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) _lib.BN_free(bignum_serial[0]) if asn1_serial == _ffi.NULL: # TODO Not tested _raise_current_error() asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) _openssl_assert(set_result == 1) def get_serial_number(self) -> int: """ Return the serial number of this certificate. :return: The serial number. :rtype: int """ asn1_serial = _lib.X509_get_serialNumber(self._x509) bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) try: hex_serial = _lib.BN_bn2hex(bignum_serial) try: hexstring_serial = _ffi.string(hex_serial) serial = int(hexstring_serial, 16) return serial finally: _lib.OPENSSL_free(hex_serial) finally: _lib.BN_free(bignum_serial) def gmtime_adj_notAfter(self, amount: int) -> None: """ Adjust the time stamp on which the certificate stops being valid. :param int amount: The number of seconds by which to adjust the timestamp. :return: ``None`` """ if not isinstance(amount, int): raise TypeError("amount must be an integer") notAfter = _lib.X509_getm_notAfter(self._x509) _lib.X509_gmtime_adj(notAfter, amount) def gmtime_adj_notBefore(self, amount: int) -> None: """ Adjust the timestamp on which the certificate starts being valid. :param amount: The number of seconds by which to adjust the timestamp. :return: ``None`` """ if not isinstance(amount, int): raise TypeError("amount must be an integer") notBefore = _lib.X509_getm_notBefore(self._x509) _lib.X509_gmtime_adj(notBefore, amount) def has_expired(self) -> bool: """ Check whether the certificate has expired. :return: ``True`` if the certificate has expired, ``False`` otherwise. :rtype: bool """ time_bytes = self.get_notAfter() if time_bytes is None: raise ValueError("Unable to determine notAfter") time_string = time_bytes.decode("utf-8") not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") UTC = datetime.timezone.utc utcnow = datetime.datetime.now(UTC).replace(tzinfo=None) return not_after < utcnow def _get_boundary_time(self, which: Any) -> Optional[bytes]: return _get_asn1_time(which(self._x509)) def get_notBefore(self) -> Optional[bytes]: """ Get the timestamp at which the certificate starts being valid. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ return self._get_boundary_time(_lib.X509_getm_notBefore) def _set_boundary_time( self, which: Callable[..., Any], when: bytes ) -> None: return _set_asn1_time(which(self._x509), when) def set_notBefore(self, when: bytes) -> None: """ Set the timestamp at which the certificate starts being valid. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ :param bytes when: A timestamp string. :return: ``None`` """ return self._set_boundary_time(_lib.X509_getm_notBefore, when) def get_notAfter(self) -> Optional[bytes]: """ Get the timestamp at which the certificate stops being valid. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ return self._get_boundary_time(_lib.X509_getm_notAfter) def set_notAfter(self, when: bytes) -> None: """ Set the timestamp at which the certificate stops being valid. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ :param bytes when: A timestamp string. :return: ``None`` """ return self._set_boundary_time(_lib.X509_getm_notAfter, when) def _get_name(self, which: Any) -> X509Name: name = X509Name.__new__(X509Name) name._name = which(self._x509) _openssl_assert(name._name != _ffi.NULL) # The name is owned by the X509 structure. As long as the X509Name # Python object is alive, keep the X509 Python object alive. name._owner = self return name def _set_name(self, which: Any, name: X509Name) -> None: if not isinstance(name, X509Name): raise TypeError("name must be an X509Name") set_result = which(self._x509, name._name) _openssl_assert(set_result == 1) def get_issuer(self) -> X509Name: """ Return the issuer of this certificate. This creates a new :class:`X509Name` that wraps the underlying issuer name field on the certificate. Modifying it will modify the underlying certificate, and will have the effect of modifying any other :class:`X509Name` that refers to this issuer. :return: The issuer of this certificate. :rtype: :class:`X509Name` """ name = self._get_name(_lib.X509_get_issuer_name) self._issuer_invalidator.add(name) return name def set_issuer(self, issuer: X509Name) -> None: """ Set the issuer of this certificate. :param issuer: The issuer. :type issuer: :py:class:`X509Name` :return: ``None`` """ self._set_name(_lib.X509_set_issuer_name, issuer) self._issuer_invalidator.clear() def get_subject(self) -> X509Name: """ Return the subject of this certificate. This creates a new :class:`X509Name` that wraps the underlying subject name field on the certificate. Modifying it will modify the underlying certificate, and will have the effect of modifying any other :class:`X509Name` that refers to this subject. :return: The subject of this certificate. :rtype: :class:`X509Name` """ name = self._get_name(_lib.X509_get_subject_name) self._subject_invalidator.add(name) return name def set_subject(self, subject: X509Name) -> None: """ Set the subject of this certificate. :param subject: The subject. :type subject: :py:class:`X509Name` :return: ``None`` """ self._set_name(_lib.X509_set_subject_name, subject) self._subject_invalidator.clear() def get_extension_count(self) -> int: """ Get the number of extensions on this certificate. :return: The number of extensions. :rtype: :py:class:`int` .. versionadded:: 0.12 """ return _lib.X509_get_ext_count(self._x509) def add_extensions( self, extensions: Iterable[_X509ExtensionInternal] ) -> None: """ Add extensions to the certificate. :param extensions: The extensions to add. :type extensions: An iterable of :py:class:`X509Extension` objects. :return: ``None`` """ for ext in extensions: if not isinstance(ext, _X509ExtensionInternal): raise ValueError("One of the elements is not an X509Extension") add_result = _lib.X509_add_ext(self._x509, ext._extension, -1) if not add_result: _raise_current_error() def get_extension(self, index: int) -> _X509ExtensionInternal: """ Get a specific extension of the certificate by index. Extensions on a certificate are kept in order. The index parameter selects which extension will be returned. :param int index: The index of the extension to retrieve. :return: The extension at the specified index. :rtype: :py:class:`X509Extension` :raises IndexError: If the extension index was out of bounds. .. versionadded:: 0.12 """ ext = _X509ExtensionInternal.__new__(_X509ExtensionInternal) ext._extension = _lib.X509_get_ext(self._x509, index) if ext._extension == _ffi.NULL: raise IndexError("extension index out of bounds") extension = _lib.X509_EXTENSION_dup(ext._extension) ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) return ext class X509StoreFlags: """ Flags for X509 verification, used to change the behavior of :class:`X509Store`. See `OpenSSL Verification Flags`_ for details. .. _OpenSSL Verification Flags: https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_flags.html """ CRL_CHECK: int = _lib.X509_V_FLAG_CRL_CHECK CRL_CHECK_ALL: int = _lib.X509_V_FLAG_CRL_CHECK_ALL IGNORE_CRITICAL: int = _lib.X509_V_FLAG_IGNORE_CRITICAL X509_STRICT: int = _lib.X509_V_FLAG_X509_STRICT ALLOW_PROXY_CERTS: int = _lib.X509_V_FLAG_ALLOW_PROXY_CERTS POLICY_CHECK: int = _lib.X509_V_FLAG_POLICY_CHECK EXPLICIT_POLICY: int = _lib.X509_V_FLAG_EXPLICIT_POLICY INHIBIT_MAP: int = _lib.X509_V_FLAG_INHIBIT_MAP CHECK_SS_SIGNATURE: int = _lib.X509_V_FLAG_CHECK_SS_SIGNATURE PARTIAL_CHAIN: int = _lib.X509_V_FLAG_PARTIAL_CHAIN class X509Store: """ An X.509 store. An X.509 store is used to describe a context in which to verify a certificate. A description of a context may include a set of certificates to trust, a set of certificate revocation lists, verification flags and more. An X.509 store, being only a description, cannot be used by itself to verify a certificate. To carry out the actual verification process, see :class:`X509StoreContext`. """ def __init__(self) -> None: store = _lib.X509_STORE_new() self._store = _ffi.gc(store, _lib.X509_STORE_free) def add_cert(self, cert: X509) -> None: """ Adds a trusted certificate to this store. Adding a certificate with this method adds this certificate as a *trusted* certificate. :param X509 cert: The certificate to add to this store. :raises TypeError: If the certificate is not an :class:`X509`. :raises OpenSSL.crypto.Error: If OpenSSL was unhappy with your certificate. :return: ``None`` if the certificate was added successfully. """ if not isinstance(cert, X509): raise TypeError() res = _lib.X509_STORE_add_cert(self._store, cert._x509) _openssl_assert(res == 1) def add_crl( self, crl: Union["_CRLInternal", x509.CertificateRevocationList] ) -> None: """ Add a certificate revocation list to this store. The certificate revocation lists added to a store will only be used if the associated flags are configured to check certificate revocation lists. .. versionadded:: 16.1.0 :param crl: The certificate revocation list to add to this store. :type crl: ``Union[CRL, cryptography.x509.CertificateRevocationList]`` :return: ``None`` if the certificate revocation list was added successfully. """ if isinstance(crl, x509.CertificateRevocationList): from cryptography.hazmat.primitives.serialization import Encoding bio = _new_mem_buf(crl.public_bytes(Encoding.DER)) openssl_crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) if openssl_crl == _ffi.NULL: _raise_current_error() crl = _ffi.gc(openssl_crl, _lib.X509_CRL_free) elif isinstance(crl, _CRLInternal): crl = crl._crl else: raise TypeError( "CRL must be of type OpenSSL.crypto.CRL or " "cryptography.x509.CertificateRevocationList" ) _openssl_assert(_lib.X509_STORE_add_crl(self._store, crl) != 0) def set_flags(self, flags: int) -> None: """ Set verification flags to this store. Verification flags can be combined by oring them together. .. note:: Setting a verification flag sometimes requires clients to add additional information to the store, otherwise a suitable error will be raised. For example, in setting flags to enable CRL checking a suitable CRL must be added to the store otherwise an error will be raised. .. versionadded:: 16.1.0 :param int flags: The verification flags to set on this store. See :class:`X509StoreFlags` for available constants. :return: ``None`` if the verification flags were successfully set. """ _openssl_assert(_lib.X509_STORE_set_flags(self._store, flags) != 0) def set_time(self, vfy_time: datetime.datetime) -> None: """ Set the time against which the certificates are verified. Normally the current time is used. .. note:: For example, you can determine if a certificate was valid at a given time. .. versionadded:: 17.0.0 :param datetime vfy_time: The verification time to set on this store. :return: ``None`` if the verification time was successfully set. """ param = _lib.X509_VERIFY_PARAM_new() param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free) _lib.X509_VERIFY_PARAM_set_time( param, calendar.timegm(vfy_time.timetuple()) ) _openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0) def load_locations( self, cafile: StrOrBytesPath, capath: Optional[StrOrBytesPath] = None ) -> None: """ Let X509Store know where we can find trusted certificates for the certificate chain. Note that the certificates have to be in PEM format. If *capath* is passed, it must be a directory prepared using the ``c_rehash`` tool included with OpenSSL. Either, but not both, of *cafile* or *capath* may be ``None``. .. note:: Both *cafile* and *capath* may be set simultaneously. Call this method multiple times to add more than one location. For example, CA certificates, and certificate revocation list bundles may be passed in *cafile* in subsequent calls to this method. .. versionadded:: 20.0 :param cafile: In which file we can find the certificates (``bytes`` or ``unicode``). :param capath: In which directory we can find the certificates (``bytes`` or ``unicode``). :return: ``None`` if the locations were set successfully. :raises OpenSSL.crypto.Error: If both *cafile* and *capath* is ``None`` or the locations could not be set for any reason. """ if cafile is None: cafile = _ffi.NULL else: cafile = _path_bytes(cafile) if capath is None: capath = _ffi.NULL else: capath = _path_bytes(capath) load_result = _lib.X509_STORE_load_locations( self._store, cafile, capath ) if not load_result: _raise_current_error() class X509StoreContextError(Exception): """ An exception raised when an error occurred while verifying a certificate using `OpenSSL.X509StoreContext.verify_certificate`. :ivar certificate: The certificate which caused verificate failure. :type certificate: :class:`X509` """ def __init__( self, message: str, errors: List[Any], certificate: X509 ) -> None: super().__init__(message) self.errors = errors self.certificate = certificate class X509StoreContext: """ An X.509 store context. An X.509 store context is used to carry out the actual verification process of a certificate in a described context. For describing such a context, see :class:`X509Store`. :param X509Store store: The certificates which will be trusted for the purposes of any verifications. :param X509 certificate: The certificate to be verified. :param chain: List of untrusted certificates that may be used for building the certificate chain. May be ``None``. :type chain: :class:`list` of :class:`X509` """ def __init__( self, store: X509Store, certificate: X509, chain: Optional[Sequence[X509]] = None, ) -> None: self._store = store self._cert = certificate self._chain = self._build_certificate_stack(chain) @staticmethod def _build_certificate_stack( certificates: Optional[Sequence[X509]], ) -> None: def cleanup(s: Any) -> None: # Equivalent to sk_X509_pop_free, but we don't # currently have a CFFI binding for that available for i in range(_lib.sk_X509_num(s)): x = _lib.sk_X509_value(s, i) _lib.X509_free(x) _lib.sk_X509_free(s) if certificates is None or len(certificates) == 0: return _ffi.NULL stack = _lib.sk_X509_new_null() _openssl_assert(stack != _ffi.NULL) stack = _ffi.gc(stack, cleanup) for cert in certificates: if not isinstance(cert, X509): raise TypeError("One of the elements is not an X509 instance") _openssl_assert(_lib.X509_up_ref(cert._x509) > 0) if _lib.sk_X509_push(stack, cert._x509) <= 0: _lib.X509_free(cert._x509) _raise_current_error() return stack @staticmethod def _exception_from_context(store_ctx: Any) -> X509StoreContextError: """ Convert an OpenSSL native context error failure into a Python exception. When a call to native OpenSSL X509_verify_cert fails, additional information about the failure can be obtained from the store context. """ message = _ffi.string( _lib.X509_verify_cert_error_string( _lib.X509_STORE_CTX_get_error(store_ctx) ) ).decode("utf-8") errors = [ _lib.X509_STORE_CTX_get_error(store_ctx), _lib.X509_STORE_CTX_get_error_depth(store_ctx), message, ] # A context error should always be associated with a certificate, so we # expect this call to never return :class:`None`. _x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) _cert = _lib.X509_dup(_x509) pycert = X509._from_raw_x509_ptr(_cert) return X509StoreContextError(message, errors, pycert) def _verify_certificate(self) -> Any: """ Verifies the certificate and runs an X509_STORE_CTX containing the results. :raises X509StoreContextError: If an error occurred when validating a certificate in the context. Sets ``certificate`` attribute to indicate which certificate caused the error. """ store_ctx = _lib.X509_STORE_CTX_new() _openssl_assert(store_ctx != _ffi.NULL) store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) ret = _lib.X509_STORE_CTX_init( store_ctx, self._store._store, self._cert._x509, self._chain ) _openssl_assert(ret == 1) ret = _lib.X509_verify_cert(store_ctx) if ret <= 0: raise self._exception_from_context(store_ctx) return store_ctx def set_store(self, store: X509Store) -> None: """ Set the context's X.509 store. .. versionadded:: 0.15 :param X509Store store: The store description which will be used for the purposes of any *future* verifications. """ self._store = store def verify_certificate(self) -> None: """ Verify a certificate in a context. .. versionadded:: 0.15 :raises X509StoreContextError: If an error occurred when validating a certificate in the context. Sets ``certificate`` attribute to indicate which certificate caused the error. """ self._verify_certificate() def get_verified_chain(self) -> List[X509]: """ Verify a certificate in a context and return the complete validated chain. :raises X509StoreContextError: If an error occurred when validating a certificate in the context. Sets ``certificate`` attribute to indicate which certificate caused the error. .. versionadded:: 20.0 """ store_ctx = self._verify_certificate() # Note: X509_STORE_CTX_get1_chain returns a deep copy of the chain. cert_stack = _lib.X509_STORE_CTX_get1_chain(store_ctx) _openssl_assert(cert_stack != _ffi.NULL) result = [] for i in range(_lib.sk_X509_num(cert_stack)): cert = _lib.sk_X509_value(cert_stack, i) _openssl_assert(cert != _ffi.NULL) pycert = X509._from_raw_x509_ptr(cert) result.append(pycert) # Free the stack but not the members which are freed by the X509 class. _lib.sk_X509_free(cert_stack) return result def load_certificate(type: int, buffer: bytes) -> X509: """ Load a certificate (X509) from the string *buffer* encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param bytes buffer: The buffer the certificate is stored in :return: The X509 object """ if isinstance(buffer, str): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: x509 = _lib.d2i_X509_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if x509 == _ffi.NULL: _raise_current_error() return X509._from_raw_x509_ptr(x509) def dump_certificate(type: int, cert: X509) -> bytes: """ Dump the certificate *cert* into a buffer string encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT) :param cert: The certificate to dump :return: The buffer with the dumped certificate in """ bio = _new_mem_buf() if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_X509(bio, cert._x509) elif type == FILETYPE_ASN1: result_code = _lib.i2d_X509_bio(bio, cert._x509) elif type == FILETYPE_TEXT: result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT" ) _openssl_assert(result_code == 1) return _bio_to_string(bio) def dump_publickey(type: int, pkey: PKey) -> bytes: """ Dump a public key to a buffer. :param type: The file type (one of :data:`FILETYPE_PEM` or :data:`FILETYPE_ASN1`). :param PKey pkey: The public key to dump :return: The buffer with the dumped key in it. :rtype: bytes """ bio = _new_mem_buf() if type == FILETYPE_PEM: write_bio = _lib.PEM_write_bio_PUBKEY elif type == FILETYPE_ASN1: write_bio = _lib.i2d_PUBKEY_bio else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") result_code = write_bio(bio, pkey._pkey) if result_code != 1: # pragma: no cover _raise_current_error() return _bio_to_string(bio) def dump_privatekey( type: int, pkey: PKey, cipher: Optional[str] = None, passphrase: Optional[PassphraseCallableT] = None, ) -> bytes: """ Dump the private key *pkey* into a buffer string encoded with the type *type*. Optionally (if *type* is :const:`FILETYPE_PEM`) encrypting it using *cipher* and *passphrase*. :param type: The file type (one of :const:`FILETYPE_PEM`, :const:`FILETYPE_ASN1`, or :const:`FILETYPE_TEXT`) :param PKey pkey: The PKey to dump :param cipher: (optional) if encrypted PEM format, the cipher to use :param passphrase: (optional) if encrypted PEM format, this can be either the passphrase to use, or a callback for providing the passphrase. :return: The buffer with the dumped key in :rtype: bytes """ bio = _new_mem_buf() if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey") if cipher is not None: if passphrase is None: raise TypeError( "if a value is given for cipher " "one must also be given for passphrase" ) cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) if cipher_obj == _ffi.NULL: raise ValueError("Invalid cipher name") else: cipher_obj = _ffi.NULL helper = _PassphraseHelper(type, passphrase) if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_PrivateKey( bio, pkey._pkey, cipher_obj, _ffi.NULL, 0, helper.callback, helper.callback_args, ) helper.raise_if_problem() elif type == FILETYPE_ASN1: result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) elif type == FILETYPE_TEXT: if _lib.EVP_PKEY_id(pkey._pkey) != _lib.EVP_PKEY_RSA: raise TypeError("Only RSA keys are supported for FILETYPE_TEXT") rsa = _ffi.gc(_lib.EVP_PKEY_get1_RSA(pkey._pkey), _lib.RSA_free) result_code = _lib.RSA_print(bio, rsa, 0) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT" ) _openssl_assert(result_code != 0) return _bio_to_string(bio) class Revoked: """ A certificate revocation. """ # https://www.openssl.org/docs/manmaster/man5/x509v3_config.html#CRL-distribution-points # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches # OCSP_crl_reason_str. We use the latter, just like the command line # program. _crl_reasons: typing.ClassVar[typing.List[bytes]] = [ b"unspecified", b"keyCompromise", b"CACompromise", b"affiliationChanged", b"superseded", b"cessationOfOperation", b"certificateHold", # b"removeFromCRL", ] def __init__(self) -> None: revoked = _lib.X509_REVOKED_new() self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free) def set_serial(self, hex_str: bytes) -> None: """ Set the serial number. The serial number is formatted as a hexadecimal number encoded in ASCII. :param bytes hex_str: The new serial number. :return: ``None`` """ bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) bignum_ptr = _ffi.new("BIGNUM**") bignum_ptr[0] = bignum_serial bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str) if not bn_result: raise ValueError("bad hex string") asn1_serial = _ffi.gc( _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), _lib.ASN1_INTEGER_free, ) _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) def get_serial(self) -> bytes: """ Get the serial number. The serial number is formatted as a hexadecimal number encoded in ASCII. :return: The serial number. :rtype: bytes """ bio = _new_mem_buf() asn1_int = _lib.X509_REVOKED_get0_serialNumber(self._revoked) _openssl_assert(asn1_int != _ffi.NULL) result = _lib.i2a_ASN1_INTEGER(bio, asn1_int) _openssl_assert(result >= 0) return _bio_to_string(bio) def _delete_reason(self) -> None: for i in range(_lib.X509_REVOKED_get_ext_count(self._revoked)): ext = _lib.X509_REVOKED_get_ext(self._revoked, i) obj = _lib.X509_EXTENSION_get_object(ext) if _lib.OBJ_obj2nid(obj) == _lib.NID_crl_reason: _lib.X509_EXTENSION_free(ext) _lib.X509_REVOKED_delete_ext(self._revoked, i) break def set_reason(self, reason: Optional[bytes]) -> None: """ Set the reason of this revocation. If :data:`reason` is ``None``, delete the reason instead. :param reason: The reason string. :type reason: :class:`bytes` or :class:`NoneType` :return: ``None`` .. seealso:: :meth:`all_reasons`, which gives you a list of all supported reasons which you might pass to this method. """ if reason is None: self._delete_reason() elif not isinstance(reason, bytes): raise TypeError("reason must be None or a byte string") else: reason = reason.lower().replace(b" ", b"") reason_code = [r.lower() for r in self._crl_reasons].index(reason) new_reason_ext = _lib.ASN1_ENUMERATED_new() _openssl_assert(new_reason_ext != _ffi.NULL) new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free) set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code) _openssl_assert(set_result != _ffi.NULL) self._delete_reason() add_result = _lib.X509_REVOKED_add1_ext_i2d( self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0 ) _openssl_assert(add_result == 1) def get_reason(self) -> Optional[bytes]: """ Get the reason of this revocation. :return: The reason, or ``None`` if there is none. :rtype: bytes or NoneType .. seealso:: :meth:`all_reasons`, which gives you a list of all supported reasons this method might return. """ for i in range(_lib.X509_REVOKED_get_ext_count(self._revoked)): ext = _lib.X509_REVOKED_get_ext(self._revoked, i) obj = _lib.X509_EXTENSION_get_object(ext) if _lib.OBJ_obj2nid(obj) == _lib.NID_crl_reason: bio = _new_mem_buf() print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0) if not print_result: print_result = _lib.M_ASN1_OCTET_STRING_print( bio, _lib.X509_EXTENSION_get_data(ext) ) _openssl_assert(print_result != 0) return _bio_to_string(bio) return None def all_reasons(self) -> List[bytes]: """ Return a list of all the supported reason strings. This list is a copy; modifying it does not change the supported reason strings. :return: A list of reason strings. :rtype: :class:`list` of :class:`bytes` """ return self._crl_reasons[:] def set_rev_date(self, when: bytes) -> None: """ Set the revocation timestamp. :param bytes when: The timestamp of the revocation, as ASN.1 TIME. :return: ``None`` """ revocationDate = _new_asn1_time(when) ret = _lib.X509_REVOKED_set_revocationDate( self._revoked, revocationDate ) _openssl_assert(ret == 1) def get_rev_date(self) -> Optional[bytes]: """ Get the revocation timestamp. :return: The timestamp of the revocation, as ASN.1 TIME. :rtype: bytes """ dt = _lib.X509_REVOKED_get0_revocationDate(self._revoked) return _get_asn1_time(dt) _RevokedInternal = Revoked utils.deprecated( Revoked, __name__, ( "CRL support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="Revoked", ) class CRL: """ A certificate revocation list. """ def __init__(self) -> None: crl = _lib.X509_CRL_new() self._crl = _ffi.gc(crl, _lib.X509_CRL_free) def to_cryptography(self) -> x509.CertificateRevocationList: """ Export as a ``cryptography`` CRL. :rtype: ``cryptography.x509.CertificateRevocationList`` .. versionadded:: 17.1.0 """ from cryptography.x509 import load_der_x509_crl der = _dump_crl_internal(FILETYPE_ASN1, self) return load_der_x509_crl(der) @classmethod def from_cryptography( cls, crypto_crl: x509.CertificateRevocationList ) -> "_CRLInternal": """ Construct based on a ``cryptography`` *crypto_crl*. :param crypto_crl: A ``cryptography`` certificate revocation list :type crypto_crl: ``cryptography.x509.CertificateRevocationList`` :rtype: CRL .. versionadded:: 17.1.0 """ if not isinstance(crypto_crl, x509.CertificateRevocationList): raise TypeError("Must be a certificate revocation list") from cryptography.hazmat.primitives.serialization import Encoding der = crypto_crl.public_bytes(Encoding.DER) return _load_crl_internal(FILETYPE_ASN1, der) def get_revoked(self) -> Optional[Tuple[_RevokedInternal, ...]]: """ Return the revocations in this certificate revocation list. These revocations will be provided by value, not by reference. That means it's okay to mutate them: it won't affect this CRL. :return: The revocations in this CRL. :rtype: :class:`tuple` of :class:`Revocation` """ results = [] revoked_stack = _lib.X509_CRL_get_REVOKED(self._crl) for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)): revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i) revoked_copy = _lib.X509_REVOKED_dup(revoked) pyrev = _RevokedInternal.__new__(_RevokedInternal) pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free) results.append(pyrev) if results: return tuple(results) return None def add_revoked(self, revoked: _RevokedInternal) -> None: """ Add a revoked (by value not reference) to the CRL structure This revocation will be added by value, not by reference. That means it's okay to mutate it after adding: it won't affect this CRL. :param Revoked revoked: The new revocation. :return: ``None`` """ copy = _lib.X509_REVOKED_dup(revoked._revoked) _openssl_assert(copy != _ffi.NULL) add_result = _lib.X509_CRL_add0_revoked(self._crl, copy) _openssl_assert(add_result != 0) def get_issuer(self) -> X509Name: """ Get the CRL's issuer. .. versionadded:: 16.1.0 :rtype: X509Name """ _issuer = _lib.X509_NAME_dup(_lib.X509_CRL_get_issuer(self._crl)) _openssl_assert(_issuer != _ffi.NULL) _issuer = _ffi.gc(_issuer, _lib.X509_NAME_free) issuer = X509Name.__new__(X509Name) issuer._name = _issuer return issuer def set_version(self, version: int) -> None: """ Set the CRL version. .. versionadded:: 16.1.0 :param int version: The version of the CRL. :return: ``None`` """ _openssl_assert(_lib.X509_CRL_set_version(self._crl, version) != 0) def set_lastUpdate(self, when: bytes) -> None: """ Set when the CRL was last updated. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ .. versionadded:: 16.1.0 :param bytes when: A timestamp string. :return: ``None`` """ lastUpdate = _new_asn1_time(when) ret = _lib.X509_CRL_set1_lastUpdate(self._crl, lastUpdate) _openssl_assert(ret == 1) def set_nextUpdate(self, when: bytes) -> None: """ Set when the CRL will next be updated. The timestamp is formatted as an ASN.1 TIME:: YYYYMMDDhhmmssZ .. versionadded:: 16.1.0 :param bytes when: A timestamp string. :return: ``None`` """ nextUpdate = _new_asn1_time(when) ret = _lib.X509_CRL_set1_nextUpdate(self._crl, nextUpdate) _openssl_assert(ret == 1) def sign(self, issuer_cert: X509, issuer_key: PKey, digest: bytes) -> None: """ Sign the CRL. Signing a CRL enables clients to associate the CRL itself with an issuer. Before a CRL is meaningful to other OpenSSL functions, it must be signed by an issuer. This method implicitly sets the issuer's name based on the issuer certificate and private key used to sign the CRL. .. versionadded:: 16.1.0 :param X509 issuer_cert: The issuer's certificate. :param PKey issuer_key: The issuer's private key. :param bytes digest: The digest method to sign the CRL with. """ digest_obj = _lib.EVP_get_digestbyname(digest) _openssl_assert(digest_obj != _ffi.NULL) _lib.X509_CRL_set_issuer_name( self._crl, _lib.X509_get_subject_name(issuer_cert._x509) ) _lib.X509_CRL_sort(self._crl) result = _lib.X509_CRL_sign(self._crl, issuer_key._pkey, digest_obj) _openssl_assert(result != 0) def export( self, cert: X509, key: PKey, type: int = FILETYPE_PEM, days: int = 100, digest: bytes = _UNSPECIFIED, # type: ignore ) -> bytes: """ Export the CRL as a string. :param X509 cert: The certificate used to sign the CRL. :param PKey key: The key used to sign the CRL. :param int type: The export format, either :data:`FILETYPE_PEM`, :data:`FILETYPE_ASN1`, or :data:`FILETYPE_TEXT`. :param int days: The number of days until the next update of this CRL. :param bytes digest: The name of the message digest to use (eg ``b"sha256"``). :rtype: bytes """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") if not isinstance(key, PKey): raise TypeError("key must be a PKey instance") if not isinstance(type, int): raise TypeError("type must be an integer") if digest is _UNSPECIFIED: raise TypeError("digest must be provided") digest_obj = _lib.EVP_get_digestbyname(digest) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") # A scratch time object to give different values to different CRL # fields sometime = _lib.ASN1_TIME_new() _openssl_assert(sometime != _ffi.NULL) sometime = _ffi.gc(sometime, _lib.ASN1_TIME_free) ret = _lib.X509_gmtime_adj(sometime, 0) _openssl_assert(ret != _ffi.NULL) ret = _lib.X509_CRL_set1_lastUpdate(self._crl, sometime) _openssl_assert(ret == 1) ret = _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60) _openssl_assert(ret != _ffi.NULL) ret = _lib.X509_CRL_set1_nextUpdate(self._crl, sometime) _openssl_assert(ret == 1) ret = _lib.X509_CRL_set_issuer_name( self._crl, _lib.X509_get_subject_name(cert._x509) ) _openssl_assert(ret == 1) sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj) if not sign_result: _raise_current_error() return _dump_crl_internal(type, self) _CRLInternal = CRL utils.deprecated( CRL, __name__, ( "CRL support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="CRL", ) class _PassphraseHelper: def __init__( self, type: int, passphrase: Optional[PassphraseCallableT], more_args: bool = False, truncate: bool = False, ) -> None: if type != FILETYPE_PEM and passphrase is not None: raise ValueError( "only FILETYPE_PEM key format supports encryption" ) self._passphrase = passphrase self._more_args = more_args self._truncate = truncate self._problems: List[Exception] = [] @property def callback(self) -> Any: if self._passphrase is None: return _ffi.NULL elif isinstance(self._passphrase, bytes) or callable(self._passphrase): return _ffi.callback("pem_password_cb", self._read_passphrase) else: raise TypeError( "Last argument must be a byte string or a callable." ) @property def callback_args(self) -> Any: if self._passphrase is None: return _ffi.NULL elif isinstance(self._passphrase, bytes) or callable(self._passphrase): return _ffi.NULL else: raise TypeError( "Last argument must be a byte string or a callable." ) def raise_if_problem(self, exceptionType: Type[Exception] = Error) -> None: if self._problems: # Flush the OpenSSL error queue try: _exception_from_error_queue(exceptionType) except exceptionType: pass raise self._problems.pop(0) def _read_passphrase( self, buf: Any, size: int, rwflag: Any, userdata: Any ) -> int: try: if callable(self._passphrase): if self._more_args: result = self._passphrase(size, rwflag, userdata) else: result = self._passphrase(rwflag) else: assert self._passphrase is not None result = self._passphrase if not isinstance(result, bytes): raise ValueError("Bytes expected") if len(result) > size: if self._truncate: result = result[:size] else: raise ValueError( "passphrase returned by callback is too long" ) for i in range(len(result)): buf[i] = result[i : i + 1] return len(result) except Exception as e: self._problems.append(e) return 0 def load_publickey(type: int, buffer: Union[str, bytes]) -> PKey: """ Load a public key from a buffer. :param type: The file type (one of :data:`FILETYPE_PEM`, :data:`FILETYPE_ASN1`). :param buffer: The buffer the key is stored in. :type buffer: A Python string object, either unicode or bytestring. :return: The PKey object. :rtype: :class:`PKey` """ if isinstance(buffer, str): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: evp_pkey = _lib.PEM_read_bio_PUBKEY( bio, _ffi.NULL, _ffi.NULL, _ffi.NULL ) elif type == FILETYPE_ASN1: evp_pkey = _lib.d2i_PUBKEY_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if evp_pkey == _ffi.NULL: _raise_current_error() pkey = PKey.__new__(PKey) pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def load_privatekey( type: int, buffer: Union[str, bytes], passphrase: Optional[PassphraseCallableT] = None, ) -> PKey: """ Load a private key (PKey) from the string *buffer* encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the key is stored in :param passphrase: (optional) if encrypted PEM format, this can be either the passphrase to use, or a callback for providing the passphrase. :return: The PKey object """ if isinstance(buffer, str): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) helper = _PassphraseHelper(type, passphrase) if type == FILETYPE_PEM: evp_pkey = _lib.PEM_read_bio_PrivateKey( bio, _ffi.NULL, helper.callback, helper.callback_args ) helper.raise_if_problem() elif type == FILETYPE_ASN1: evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if evp_pkey == _ffi.NULL: _raise_current_error() pkey = PKey.__new__(PKey) pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) return pkey def dump_certificate_request(type: int, req: X509Req) -> bytes: """ Dump the certificate request *req* into a buffer string encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param req: The certificate request to dump :return: The buffer with the dumped certificate request in """ bio = _new_mem_buf() if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) elif type == FILETYPE_ASN1: result_code = _lib.i2d_X509_REQ_bio(bio, req._req) elif type == FILETYPE_TEXT: result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT" ) _openssl_assert(result_code != 0) return _bio_to_string(bio) _dump_certificate_request_internal = dump_certificate_request utils.deprecated( dump_certificate_request, __name__, ( "CSR support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="dump_certificate_request", ) def load_certificate_request(type: int, buffer: bytes) -> X509Req: """ Load a certificate request (X509Req) from the string *buffer* encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the certificate request is stored in :return: The X509Req object """ if isinstance(buffer, str): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") _openssl_assert(req != _ffi.NULL) x509req = _X509ReqInternal.__new__(_X509ReqInternal) x509req._req = _ffi.gc(req, _lib.X509_REQ_free) return x509req _load_certificate_request_internal = load_certificate_request utils.deprecated( load_certificate_request, __name__, ( "CSR support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="load_certificate_request", ) def sign(pkey: PKey, data: Union[str, bytes], digest: str) -> bytes: """ Sign a data string using the given key and message digest. :param pkey: PKey to sign with :param data: data to be signed :param digest: message digest to use :return: signature .. versionadded:: 0.11 """ data = _text_to_bytes_and_warn("data", data) digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") md_ctx = _lib.EVP_MD_CTX_new() md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_free) _lib.EVP_SignInit(md_ctx, digest_obj) _lib.EVP_SignUpdate(md_ctx, data, len(data)) length = _lib.EVP_PKEY_size(pkey._pkey) _openssl_assert(length > 0) signature_buffer = _ffi.new("unsigned char[]", length) signature_length = _ffi.new("unsigned int *") final_result = _lib.EVP_SignFinal( md_ctx, signature_buffer, signature_length, pkey._pkey ) _openssl_assert(final_result == 1) return _ffi.buffer(signature_buffer, signature_length[0])[:] utils.deprecated( sign, __name__, "sign() is deprecated. Use the equivalent APIs in cryptography.", DeprecationWarning, name="sign", ) def verify( cert: X509, signature: bytes, data: Union[str, bytes], digest: str ) -> None: """ Verify the signature for a data string. :param cert: signing certificate (X509 object) corresponding to the private key which generated the signature. :param signature: signature returned by sign function :param data: data to be verified :param digest: message digest to use :return: ``None`` if the signature is correct, raise exception otherwise. .. versionadded:: 0.11 """ data = _text_to_bytes_and_warn("data", data) digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") pkey = _lib.X509_get_pubkey(cert._x509) _openssl_assert(pkey != _ffi.NULL) pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) md_ctx = _lib.EVP_MD_CTX_new() md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_free) _lib.EVP_VerifyInit(md_ctx, digest_obj) _lib.EVP_VerifyUpdate(md_ctx, data, len(data)) verify_result = _lib.EVP_VerifyFinal( md_ctx, signature, len(signature), pkey ) if verify_result != 1: _raise_current_error() utils.deprecated( verify, __name__, "verify() is deprecated. Use the equivalent APIs in cryptography.", DeprecationWarning, name="verify", ) def dump_crl(type: int, crl: _CRLInternal) -> bytes: """ Dump a certificate revocation list to a buffer. :param type: The file type (one of ``FILETYPE_PEM``, ``FILETYPE_ASN1``, or ``FILETYPE_TEXT``). :param CRL crl: The CRL to dump. :return: The buffer with the CRL. :rtype: bytes """ bio = _new_mem_buf() if type == FILETYPE_PEM: ret = _lib.PEM_write_bio_X509_CRL(bio, crl._crl) elif type == FILETYPE_ASN1: ret = _lib.i2d_X509_CRL_bio(bio, crl._crl) elif type == FILETYPE_TEXT: ret = _lib.X509_CRL_print(bio, crl._crl) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT" ) _openssl_assert(ret == 1) return _bio_to_string(bio) _dump_crl_internal = dump_crl utils.deprecated( dump_crl, __name__, ( "CRL support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="dump_crl", ) def load_crl(type: int, buffer: Union[str, bytes]) -> _CRLInternal: """ Load Certificate Revocation List (CRL) data from a string *buffer*. *buffer* encoded with the type *type*. :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the CRL is stored in :return: The CRL object """ if isinstance(buffer, str): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if crl == _ffi.NULL: _raise_current_error() result = _CRLInternal.__new__(_CRLInternal) result._crl = _ffi.gc(crl, _lib.X509_CRL_free) return result _load_crl_internal = load_crl utils.deprecated( load_crl, __name__, ( "CRL support in pyOpenSSL is deprecated. You should use the APIs " "in cryptography." ), DeprecationWarning, name="load_crl", ) pyopenssl-24.2.1/src/OpenSSL/debug.py000066400000000000000000000017601465571700000173570ustar00rootroot00000000000000import ssl import sys import cffi import cryptography import OpenSSL.SSL from . import version _env_info = """\ pyOpenSSL: {pyopenssl} cryptography: {cryptography} cffi: {cffi} cryptography's compiled against OpenSSL: {crypto_openssl_compile} cryptography's linked OpenSSL: {crypto_openssl_link} Python's OpenSSL: {python_openssl} Python executable: {python} Python version: {python_version} Platform: {platform} sys.path: {sys_path}""".format( pyopenssl=version.__version__, crypto_openssl_compile=OpenSSL._util.ffi.string( OpenSSL._util.lib.OPENSSL_VERSION_TEXT, ).decode("ascii"), crypto_openssl_link=OpenSSL.SSL.SSLeay_version( OpenSSL.SSL.SSLEAY_VERSION ).decode("ascii"), python_openssl=getattr(ssl, "OPENSSL_VERSION", "n/a"), cryptography=cryptography.__version__, cffi=cffi.__version__, python=sys.executable, python_version=sys.version, platform=sys.platform, sys_path=sys.path, ) if __name__ == "__main__": print(_env_info) pyopenssl-24.2.1/src/OpenSSL/py.typed000066400000000000000000000000001465571700000174000ustar00rootroot00000000000000pyopenssl-24.2.1/src/OpenSSL/rand.py000066400000000000000000000020551465571700000172130ustar00rootroot00000000000000""" PRNG management routines, thin wrappers. """ from OpenSSL._util import lib as _lib def add(buffer: bytes, entropy: int) -> None: """ Mix bytes from *string* into the PRNG state. The *entropy* argument is (the lower bound of) an estimate of how much randomness is contained in *string*, measured in bytes. For more information, see e.g. :rfc:`1750`. This function is only relevant if you are forking Python processes and need to reseed the CSPRNG after fork. :param buffer: Buffer with random data. :param entropy: The entropy (in bytes) measurement of the buffer. :return: :obj:`None` """ if not isinstance(buffer, bytes): raise TypeError("buffer must be a byte string") if not isinstance(entropy, int): raise TypeError("entropy must be an integer") _lib.RAND_add(buffer, len(buffer), entropy) def status() -> int: """ Check whether the PRNG has been seeded with enough data. :return: 1 if the PRNG is seeded enough, 0 otherwise. """ return _lib.RAND_status() pyopenssl-24.2.1/src/OpenSSL/version.py000066400000000000000000000012011465571700000177440ustar00rootroot00000000000000# Copyright (C) AB Strakt # Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ pyOpenSSL - A simple wrapper around the OpenSSL library """ __all__ = [ "__author__", "__copyright__", "__email__", "__license__", "__summary__", "__title__", "__uri__", "__version__", ] __version__ = "24.2.1" __title__ = "pyOpenSSL" __uri__ = "https://pyopenssl.org/" __summary__ = "Python wrapper module around the OpenSSL library" __author__ = "The pyOpenSSL developers" __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" __copyright__ = f"Copyright 2001-2024 {__author__}" pyopenssl-24.2.1/tests/000077500000000000000000000000001465571700000150035ustar00rootroot00000000000000pyopenssl-24.2.1/tests/__init__.py000066400000000000000000000001751465571700000171170ustar00rootroot00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Package containing unit tests for :py:mod:`OpenSSL`. """ pyopenssl-24.2.1/tests/conftest.py000066400000000000000000000010651465571700000172040ustar00rootroot00000000000000# Copyright (c) The pyOpenSSL developers # See LICENSE for details. from tempfile import mktemp import pytest def pytest_report_header(config): import cryptography import OpenSSL.SSL return ( f"OpenSSL: {OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION)}\n" f"cryptography: {cryptography.__version__}" ) @pytest.fixture def tmpfile(tmpdir): """ Return UTF-8-encoded bytes of a path to a tmp file. The file will be cleaned up after the test run. """ return mktemp(dir=tmpdir.dirname).encode("utf-8") pyopenssl-24.2.1/tests/memdbg.py000066400000000000000000000037111465571700000166120ustar00rootroot00000000000000import sys import traceback from cffi import api as _api sys.modules["ssl"] = None sys.modules["_hashlib"] = None _ffi = _api.FFI() _ffi.cdef( """ void *malloc(size_t size); void free(void *ptr); void *realloc(void *ptr, size_t size); int CRYPTO_set_mem_functions( void *(*m)(size_t),void *(*r)(void *,size_t), void (*f)(void *)); int backtrace(void **buffer, int size); char **backtrace_symbols(void *const *buffer, int size); void backtrace_symbols_fd(void *const *buffer, int size, int fd); """ ) _api = _ffi.verify( """ #include #include #include """, libraries=["crypto"], ) C = _ffi.dlopen(None) verbose = False def log(s): if verbose: print(s) def _backtrace(): buf = _ffi.new("void*[]", 64) result = _api.backtrace(buf, len(buf)) strings = _api.backtrace_symbols(buf, result) stack = [_ffi.string(strings[i]) for i in range(result)] C.free(strings) return stack @_ffi.callback("void*(*)(size_t)") def malloc(n): memory = C.malloc(n) python_stack = traceback.extract_stack(limit=3) c_stack = _backtrace() heap[memory] = [(n, python_stack, c_stack)] log("malloc(%d) -> %s" % (n, memory)) return memory @_ffi.callback("void*(*)(void*, size_t)") def realloc(p, n): memory = C.realloc(p, n) old = heap.pop(p) python_stack = traceback.extract_stack(limit=3) c_stack = _backtrace() old.append((n, python_stack, c_stack)) heap[memory] = old log("realloc(0x%x, %d) -> %s" % (int(_ffi.cast("int", p)), n, memory)) return memory @_ffi.callback("void(*)(void*)") def free(p): if p != _ffi.NULL: C.free(p) del heap[p] log("free(0x{:x})".format(int(_ffi.cast("int", p)))) if _api.CRYPTO_set_mem_functions(malloc, realloc, free): log("Enabled memory debugging") heap = {} else: log("Failed to enable memory debugging") heap = None pyopenssl-24.2.1/tests/test_crypto.py000066400000000000000000004643251465571700000177520ustar00rootroot00000000000000# Copyright (c) Jean-Paul Calderone # See LICENSE file for details. """ Unit tests for :py:mod:`OpenSSL.crypto`. """ import base64 import sys import warnings from datetime import datetime, timedelta, timezone from subprocess import PIPE, Popen import pytest from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ( dsa, ec, ed448, ed25519, rsa, ) from OpenSSL._util import ffi as _ffi from OpenSSL._util import lib as _lib from OpenSSL.crypto import ( FILETYPE_ASN1, FILETYPE_PEM, FILETYPE_TEXT, TYPE_DSA, TYPE_RSA, X509, Error, PKey, X509Name, X509Req, X509Store, X509StoreContext, X509StoreContextError, X509StoreFlags, dump_certificate, dump_certificate_request, dump_privatekey, dump_publickey, get_elliptic_curve, get_elliptic_curves, load_certificate, load_certificate_request, load_privatekey, load_publickey, sign, verify, ) with pytest.warns(DeprecationWarning): from OpenSSL.crypto import ( CRL, Revoked, X509Extension, dump_crl, load_crl, ) from .util import ( NON_ASCII, WARNING_TYPE_EXPECTED, EqualityTestsMixin, is_consistent_type, ) def normalize_privatekey_pem(pem): return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem)) def utcnow(): return datetime.now(timezone.utc).replace(tzinfo=None) GOOD_CIPHER = "blowfish" BAD_CIPHER = "zippers" GOOD_DIGEST = "SHA256" BAD_DIGEST = "monkeys" old_root_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2 NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy 2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF 1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn -----END CERTIFICATE----- """ root_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIE7jCCA1agAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAwODAyMTcxMTE5 WhcNNDcxMjIwMTcxMTE5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO BgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMTD1Rlc3Rp bmcgUm9vdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALpY5jb+ S7AUbx9gzN06wkqUeb+eNLTjCOKofiMTn8Y0TqCA2ZyY3XMcNBMaIS7hdFTgmmqt fFntYobxLAl/twfbz9AnRaVDh2HyUvHvMBxKn1HSDLALLtqdF0pcXIjP04S7NKPQ Umgkv2H0KwcUpYlgjTFtXRiP+7wDSiQeP1YVSriEoE0TXK14F8np6ZKK0oQ+u16d Wn3MGQwFzS+Ipgoz0jbi5D2KzmK2dzHdxY8M2Dktkz/W3DUfUwaTohYed2DG39LP NUFOxekgXdIZ3vQbDfsEQt27TUzOztbo/BqK7YkRLzzOQFz+dKAxH6Hy6Bu9op7e DWS9TfD/+UmDxr3IeoLMpmUBKxmzTC4qpej+W1UuCE12dMo4LoadlkG+/l1oABqd Ucf45WgaFk3xpyEuGnDxjs6rqYPoEapIichxN2fgN+jkgH9ed44r0yOoVeG2pmwD YFCCxzkmiuzLADlfM1LUzqUNKVFcOakD3iujHEalnDIJsc/znYsqaRvCkQIDAQAB o4G7MIG4MB0GA1UdDgQWBBSDVXctXiHxSQwJJOdUCRKNyH4ErjCBiAYDVR0jBIGA MH6AFINVdy1eIfFJDAkk51QJEo3IfgSuoVykWjBYMQswCQYDVQQGEwJVUzELMAkG A1UECBMCSUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAW BgNVBAMTD1Rlc3RpbmcgUm9vdCBDQYIIPQzE4MbeufQwDAYDVR0TBAUwAwEB/zAN BgkqhkiG9w0BAQsFAAOCAYEAFIMFxLHaVDY/nsbYzI7+zxe4GJeUqRIj2g4XK/nF 6lHLRFL2YP5yJ+Jm4JDkoZqKq/tcEQLIssQS++s6tBSdvFwdY6imfpEZgFPuodrZ KbYm4Xuouw09EQCEjPxBOQ1NEcPuwuDtvD6/BOfm3SRFRTq/gQwxKlZ7C/4l8b1+ OQPIUryqdlFBpyE/M95GzaNdmkQx41PevEih2nqWnbTsXLeiSXLGoubMTxKEK4T+ J7Ci2KTRJ3SYMgTNU6MNcl7b9Tpw9/KVG80IbpzNQ1LDh3ZtkOfqoou1lmBTeNPu g2C/oiW6lVAmZx1TL9gbUtkJ0Q2iW4D9TF+zuYi2qpbVU3RvoqK25x3AuIWf4JOL 3cTNjJ/3zmGSORRJvcGyvVnL30R+vwpaxvyuqMjz3kBjkK2Z2pvElZMJiZhbGG7k MHZQ5A26v0/iQVno6FRv3cQb9EeAZtNHcIEgsNhPZ53XVnwZ58ByvATMLKNN8dWF Q+8Bbr7QFxeWvQfHYX2yaQZ/ -----END CERTIFICATE----- """ root_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- MIIG5AIBAAKCAYEAuljmNv5LsBRvH2DM3TrCSpR5v540tOMI4qh+IxOfxjROoIDZ nJjdcxw0ExohLuF0VOCaaq18We1ihvEsCX+3B9vP0CdFpUOHYfJS8e8wHEqfUdIM sAsu2p0XSlxciM/ThLs0o9BSaCS/YfQrBxSliWCNMW1dGI/7vANKJB4/VhVKuISg TRNcrXgXyenpkorShD67Xp1afcwZDAXNL4imCjPSNuLkPYrOYrZ3Md3FjwzYOS2T P9bcNR9TBpOiFh53YMbf0s81QU7F6SBd0hne9BsN+wRC3btNTM7O1uj8GortiREv PM5AXP50oDEfofLoG72int4NZL1N8P/5SYPGvch6gsymZQErGbNMLiql6P5bVS4I TXZ0yjguhp2WQb7+XWgAGp1Rx/jlaBoWTfGnIS4acPGOzqupg+gRqkiJyHE3Z+A3 6OSAf153jivTI6hV4bambANgUILHOSaK7MsAOV8zUtTOpQ0pUVw5qQPeK6McRqWc Mgmxz/OdiyppG8KRAgMBAAECggGAGi6Tafagu8SjOE1pe0veMIxb7shTr3aWsQHr dxIyyK5gvbxc1tvDgYDc8DIjp2qV5bcI+yQU7K2lwj/waAVBuiDwOdbKukWap/Bc JxHsOI1jhSN2FOX9V0nrE8+WUMKifWuwIbQLYAaJvUGJKh2EhKDENcWf5uuT+v6b VCfLzlR/gx1fSHUH+Hd/ICd1YdmPanVF7i09oZ8jhcTq51rTuWs+heerGdp+1O++ H4uBTnAHkUEOB1Iw7mXQTIRBqcntzob/TJrDKycdbFHEeRR0L1hALGEVftq7zI6F BA9caO1W7HkcVmeT6HATIEIGG5H7QAwSfZflJ/82ZXtDemqhBRVwQ2Fx/99wW3r9 puUvJyLbba7NCwL1+P9w8ebr00kFyYoy6rE1JjqlE+9ZHwakZUWTA1lMOGWNEkRS bKZNHgrngs2zk5qCYRllmsBZ3obdufnP/wyag+BFVniAIN3a08y46SYmgYTeLdBX /DHSZIKWI9rBiNg6Qw49N+06XwiBAoHBAOMZQbRT8hEffRFbxcsRdJ4dUCM1RAXV /IMLeVQgKEWETX3pCydpQ2v65fJPACfLLwwRMq4BX4YpJVHCk6BZh/2zx8T1spmJ uBkHH6+VYgB9JVU0hv/APAjTZxdBjdhkaXVxccpmBBJqKKwOGf3nRVhmMsItBx2x ZCz+x50+buRMTKsF+FeK2Dr2e9WrfMkOJ3nQFwbGvOBIQeXKmu0wYUVyebnCdZW5 pKI0Co7wp9soCa02YvTFR8n2kxMe9Y91jQKBwQDSD/xSsRfgDT0uiEwazVQ2D/42 96U2MYe+k+p1GHBnjIX4eRPcWOnQNUd/QVy1UK4bQg1dVZi+NQJ1YS3mKNCpqOaK ovrgHHmYC1YIn8Xmq2YGzrm/JLwXw0BkPhHp/1yQVPVgyFKeNa3fSa0tkqCed5rs erM8090IIzWPzKtXId8Db4i0xHkDzP7xDThb6pPNx5bvAaempJRDLtN9xP/hQRyh xZ/MECKGRgyAVfndIZaI82kuUQFlnPMqk4FxFhUCgcAhnMdgzVvytNpqC09HMxoz nNsTmvqqcnWhX71hejD7uQ1PKYMBHk9gWA5YwuCfAy+/dXwuzP06ejSP2WDIRvgd 0NIskMESgJPDAI7sCgwrTlqMNe4VRHqeQ8vqYUWBVbtWKqhQ8LCBmTzT2nJ2ZhiZ cObqXofDGVJeZodc+rSnDbP7TDLpoh9G+txxT6R0jafCG86MrjWebJN0U3yCxrpe 8QabO/DzbDq110YIyg3OHirwfDBBUkHB3sD9/4MQ7LECgcEAs2UFhxVIn4aO5ott +0G5lkYIQ6cwx9x64i3ugDvz2uruiunUJU0luTOXML2AUDRrzEmXokr0nBQnWlk4 2qOmuA3PfTx85iJLUab0vX69gyaDhnLLvMrBe8W62yELKXx076ouuI27yPNs3xFL vWzIkSzx+N0870i8LjPrjTgsZ8g8bfG1nTNhafaLDw/MPutReN7oLouKQs2w9MMr yPAR2qxBqIJe2uY4pdVy3bMPJWOG7MR74hs6By6HmKfKVuqVAoHBAMRSefX1QtfS 3wWpQhkE7Sooco4LI8kfNncZ2gzNDbYf6aOkgzv0/SWJh+CdcKep9xk12O02Lpsm SsPYeYlPDCCvyJYGpR19QocYp6JCaemb7uMd6FuPHSHUgyoR4GS8PUuIbiRnpPxN 4ta7VzmIZOCFu5e+vOq1NwTd0hR6sy5uNsTHV5ezOOqz2SB+yTRMDPr7cW0dMSJ8 jsvxvqVnkIhWeuP9GIb6XUhq74huGZ0Hpaxe6xG34QYiBpr/O3O/ew== -----END RSA PRIVATE KEY----- """ root_key_der = base64.b64decode( """ MIIG5AIBAAKCAYEAuljmNv5LsBRvH2DM3TrCSpR5v540tOMI4qh+IxOfxjROoIDZ nJjdcxw0ExohLuF0VOCaaq18We1ihvEsCX+3B9vP0CdFpUOHYfJS8e8wHEqfUdIM sAsu2p0XSlxciM/ThLs0o9BSaCS/YfQrBxSliWCNMW1dGI/7vANKJB4/VhVKuISg TRNcrXgXyenpkorShD67Xp1afcwZDAXNL4imCjPSNuLkPYrOYrZ3Md3FjwzYOS2T P9bcNR9TBpOiFh53YMbf0s81QU7F6SBd0hne9BsN+wRC3btNTM7O1uj8GortiREv PM5AXP50oDEfofLoG72int4NZL1N8P/5SYPGvch6gsymZQErGbNMLiql6P5bVS4I TXZ0yjguhp2WQb7+XWgAGp1Rx/jlaBoWTfGnIS4acPGOzqupg+gRqkiJyHE3Z+A3 6OSAf153jivTI6hV4bambANgUILHOSaK7MsAOV8zUtTOpQ0pUVw5qQPeK6McRqWc Mgmxz/OdiyppG8KRAgMBAAECggGAGi6Tafagu8SjOE1pe0veMIxb7shTr3aWsQHr dxIyyK5gvbxc1tvDgYDc8DIjp2qV5bcI+yQU7K2lwj/waAVBuiDwOdbKukWap/Bc JxHsOI1jhSN2FOX9V0nrE8+WUMKifWuwIbQLYAaJvUGJKh2EhKDENcWf5uuT+v6b VCfLzlR/gx1fSHUH+Hd/ICd1YdmPanVF7i09oZ8jhcTq51rTuWs+heerGdp+1O++ H4uBTnAHkUEOB1Iw7mXQTIRBqcntzob/TJrDKycdbFHEeRR0L1hALGEVftq7zI6F BA9caO1W7HkcVmeT6HATIEIGG5H7QAwSfZflJ/82ZXtDemqhBRVwQ2Fx/99wW3r9 puUvJyLbba7NCwL1+P9w8ebr00kFyYoy6rE1JjqlE+9ZHwakZUWTA1lMOGWNEkRS bKZNHgrngs2zk5qCYRllmsBZ3obdufnP/wyag+BFVniAIN3a08y46SYmgYTeLdBX /DHSZIKWI9rBiNg6Qw49N+06XwiBAoHBAOMZQbRT8hEffRFbxcsRdJ4dUCM1RAXV /IMLeVQgKEWETX3pCydpQ2v65fJPACfLLwwRMq4BX4YpJVHCk6BZh/2zx8T1spmJ uBkHH6+VYgB9JVU0hv/APAjTZxdBjdhkaXVxccpmBBJqKKwOGf3nRVhmMsItBx2x ZCz+x50+buRMTKsF+FeK2Dr2e9WrfMkOJ3nQFwbGvOBIQeXKmu0wYUVyebnCdZW5 pKI0Co7wp9soCa02YvTFR8n2kxMe9Y91jQKBwQDSD/xSsRfgDT0uiEwazVQ2D/42 96U2MYe+k+p1GHBnjIX4eRPcWOnQNUd/QVy1UK4bQg1dVZi+NQJ1YS3mKNCpqOaK ovrgHHmYC1YIn8Xmq2YGzrm/JLwXw0BkPhHp/1yQVPVgyFKeNa3fSa0tkqCed5rs erM8090IIzWPzKtXId8Db4i0xHkDzP7xDThb6pPNx5bvAaempJRDLtN9xP/hQRyh xZ/MECKGRgyAVfndIZaI82kuUQFlnPMqk4FxFhUCgcAhnMdgzVvytNpqC09HMxoz nNsTmvqqcnWhX71hejD7uQ1PKYMBHk9gWA5YwuCfAy+/dXwuzP06ejSP2WDIRvgd 0NIskMESgJPDAI7sCgwrTlqMNe4VRHqeQ8vqYUWBVbtWKqhQ8LCBmTzT2nJ2ZhiZ cObqXofDGVJeZodc+rSnDbP7TDLpoh9G+txxT6R0jafCG86MrjWebJN0U3yCxrpe 8QabO/DzbDq110YIyg3OHirwfDBBUkHB3sD9/4MQ7LECgcEAs2UFhxVIn4aO5ott +0G5lkYIQ6cwx9x64i3ugDvz2uruiunUJU0luTOXML2AUDRrzEmXokr0nBQnWlk4 2qOmuA3PfTx85iJLUab0vX69gyaDhnLLvMrBe8W62yELKXx076ouuI27yPNs3xFL vWzIkSzx+N0870i8LjPrjTgsZ8g8bfG1nTNhafaLDw/MPutReN7oLouKQs2w9MMr yPAR2qxBqIJe2uY4pdVy3bMPJWOG7MR74hs6By6HmKfKVuqVAoHBAMRSefX1QtfS 3wWpQhkE7Sooco4LI8kfNncZ2gzNDbYf6aOkgzv0/SWJh+CdcKep9xk12O02Lpsm SsPYeYlPDCCvyJYGpR19QocYp6JCaemb7uMd6FuPHSHUgyoR4GS8PUuIbiRnpPxN 4ta7VzmIZOCFu5e+vOq1NwTd0hR6sy5uNsTHV5ezOOqz2SB+yTRMDPr7cW0dMSJ8 jsvxvqVnkIhWeuP9GIb6XUhq74huGZ0Hpaxe6xG34QYiBpr/O3O/ew==' """ ) normalized_root_key_pem = normalize_privatekey_pem(root_key_pem) intermediate_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIEXDCCAsSgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQELBQAw WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAw ODAyMTcxMTIwWhcNNDcxMjIwMTcxMTIwWjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIIBojANBgkqhkiG9w0B AQEFAAOCAY8AMIIBigKCAYEAo3rOxOVrdLdRsR1o0+JG7MRpCtMoafA63TM/DczL Q4jURv5MzyTE7FFdXq4xNJRYgD16vUavZluQGj30+5Lkt07CuO/BK3itl8UW+dsH p95gzBvgnj5AVZGkNOQ0Y4CbXO087Ywep7tpBfZ5fzURLeH+OHQGseEFZ5e0w8Az AarWu+Ez5RGpkaZ61iiJa53mAgkrjw+o83UrpDT2nrXiyR6Fx4K4eb1rarodWqGv jSkdT5MA4i0gDhsIBnTarPB+0KM8M7od8DkLsTHBt4rYYCHgCX1bWavzGlqPEw9h ksK+LAbQKD9J2AxYDkL0PAeUuvWMhxEmN6hXePiw63sJzukRunAvut5A2+42JMkW guDyqIvAjlCYcIyBvUbphP3qSFqww/hpZ2wh5UZOc1mzYJKR9MgI8/UhRJEJ7NyY pF24EJbisjNE30ot8aM2/5cI5KevclcuPJWH8PjT/i1VnNpM4S8MqoPw6F+d75d/ CtfI+LLfns4k3G9I+Qgxmpa5AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJ KoZIhvcNAQELBQADggGBAFVQ3Dmljrnbjys9ZIqcTs/B5ktKUAU2KNMO9TmoFymE YhHKbCb5u/CnWq3jtBW6jgkQHrhfY9leUlH87BkB2o16BcSKjHknHZ2MCdEvQvOM /nkkMDkOEoRn8mfCCxxgt8Kxf07wHDcnKoeJ3h9BXIl6nyJqJAcVWEJm1d75ayDG 0Kr0z+LcqMtQqYI0csK/XDQkunlE95qti1HzxW+JeAf6nRkr7RNZLtGmUGAMfyBK 9A0Db8QLR7O92YEmwoXtp+euN6uDdjw4A7KHjNXMdvqZoRfbZEA9c6XJTBj22h87 gYUFRVpkNDrC/c9u6WgA943yMgFCwjrlTsmi+uoweT9U5r4TA+dVCDAv943aWCNm A+TiuIXlJAHl2PlH7Umu/oMQKDEt+0n4QcQLBZyK3CYU5kg+ms9vOvE19Lhp8HeS xqm6dwKpdm7/8EfGNW3s8Gm4KM26mb7dtSdHJFuR/BQ5y/cn4qIMyeGfHvsVew+2 neyFR2Oc/nUlZMKfyHI+pA== -----END CERTIFICATE----- """ intermediate_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- MIIG4gIBAAKCAYEAo3rOxOVrdLdRsR1o0+JG7MRpCtMoafA63TM/DczLQ4jURv5M zyTE7FFdXq4xNJRYgD16vUavZluQGj30+5Lkt07CuO/BK3itl8UW+dsHp95gzBvg nj5AVZGkNOQ0Y4CbXO087Ywep7tpBfZ5fzURLeH+OHQGseEFZ5e0w8AzAarWu+Ez 5RGpkaZ61iiJa53mAgkrjw+o83UrpDT2nrXiyR6Fx4K4eb1rarodWqGvjSkdT5MA 4i0gDhsIBnTarPB+0KM8M7od8DkLsTHBt4rYYCHgCX1bWavzGlqPEw9hksK+LAbQ KD9J2AxYDkL0PAeUuvWMhxEmN6hXePiw63sJzukRunAvut5A2+42JMkWguDyqIvA jlCYcIyBvUbphP3qSFqww/hpZ2wh5UZOc1mzYJKR9MgI8/UhRJEJ7NyYpF24EJbi sjNE30ot8aM2/5cI5KevclcuPJWH8PjT/i1VnNpM4S8MqoPw6F+d75d/CtfI+LLf ns4k3G9I+Qgxmpa5AgMBAAECggGAc0i/V4qR5JUCPuyGaCVB7uXzTXbrIQoP+L2S 0aCCFvX+/LGIaOt9E0mtln8wo+uZHZY9YAzg1EXtsRPQFzjXoY0hNFme15EamdSb B0e2dmMTz9w44l7z72PtcH8dkq224ilKthoB5Db9MP9HXrWFj9228QihT/9nWE5b Y0++qIZZN9TwS7HQ6q2EIlIj1ohbE0R0O0bH1ifixsGyyOlrLHkhzjgY74Dspy7o VGmA6wL7cIoyLU21NT1Kw4LUUvCk3MTd62gIg43qLsoLJ1AVZg9AmLmhZn4HiGZa tiE1+Iz70E+qxIXDQTip/EY4qe9HHYM2VccjlMQsLLCw5Y2CJL0xbRUSPkKev+Us PyasHgxPP6s5sHTKm0fee+riJrR+CqODGT45CirJr+WjDznlJETgVDW5DmtTWGVW 2WeBarXdYOn4S+uK3Pe3aTAiE9Uw7KrOdJqrWg89YFnMWw4HlMz0369HCUv5BqSg qtrJ7iPJhN5MMhA4Te2Rnc5onqEhAoHBANKmZP4/g5RoYy6Gjkwe9PSgp9URxCJt VHiE5r33jXxOMw2lJQD8JVLmWuNTbKEClj6Rd/5OzM2q2icYDu0k/wcX+BgXg5b2 ozyfjzgnqddKs8SlNd9oc2xiFRLgBkdHI5XFQlcp6vpEM+m47azEw72RtsKObN0g PZwSK8RWTj4zCXTdYMdr+gbdOA3fzUztckHLJQeS42JT3XJVSrSzFyXuVgXmdnS9 bQ2dUfPT+JzwHy/HMmaBDM7fodDgv/XUywKBwQDGrLTomybbfc3ilZv+CZMW7bTy pX8ydj6GSIBWLd+7gduQHYqam5gNK2v4BKPVHXMMcRZNIIId3FZztMaP3vkWQXIG /bNBnL4Aa8mZFUle1VGoPZxMt1aaVLv3UqWi47ptciA6uZCuc/6si3THTsNr/7kR k6A7UmA0CRYWzuezRsbEGRXZCCFGwJm2WCfewjNRqH/I+Kvfj06AddKkwByujfc6 zQDH/m0QFNAKgEZYvFOL/Yd2cuFhU2OPUO4jFgsCgcBXRbjx3T6WbekpjXXG88xo zWa7T/ECkmk8xVMTwUxNA9kC/jimf9C219kv9ZA75OZ6ZaphIiSX0QEw0Tbd6UX/ ml6fHJ7YHLbklvavPT+QgtKX1hrLxGqNrNUuTMJNJZwIoQErO6KurTMU0hkmSx8N myEs2fUgaAsebijT3y3rdxmj4VQHSyT7Uwu2M9LK3FVKDO/6g1DRnA1TISMiWlBs 1qGtMB5Dn3de/J7Hdjq6SoGhOdYXwb+ctepEr9jX8KECgcAE2nk86XVkjUk3TNJX vWIjgEEYYGSgFfVnEGRaNpqtmPmFJsOZDU4EnFfx4iMidKq31hdmYPHsytIt12+2 WgsZuRWRCCeV5b9agUeWfsehEnMBOigUU7JA6OsCmrlDJm8Kd2xEIv5e1KSXEH0U 1V6+x6t8u2+Bo3yIKOSqP/m3DnaSmc5H1AQEF3Zp1vN6ZKIeT5B3l2OTfYu8ZaR0 s+C/fuZYQGPRfuypJOkEKKgPSOJ9m/7wLNRGrWPUP3Th1IsCgcBb2O9ROv793a3x PtW4qzkqF69KKc2O/vT819NBQjGopQetOcsY3VHp0eJMv85ut4cCeqScAfdtFIiC ScnrBO4JtdE6FkTY1k8el1DrctrUR3PZ2rt3m5k2XfPDGEypH3BReD3dHUe2M99D +dceH46rKyMXQ2lLA3iyzGE6NyWUTZ6co35/Qm2n8lV9IG1CuX5HVAVrr2osLG93 zZvFSeTrN2MZvmelhS6aUJCV/PxiQPHlou8vLU6zzfPMSERTjOI= -----END RSA PRIVATE KEY----- """ server_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIEKTCCApGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHQ2hpY2FnbzEQMA4GA1UECgwH VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTIwMDgwMjE3MTEy MFoXDTQ3MTIyMDE3MTEyMFowGDEWMBQGA1UEAwwNbG92ZWx5IHNlcnZlcjCCAaIw DQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKU9txhKg6Nc0dVK9Vv4MYuYP6Hs oR483+wC53V8axkfy2TynrBSug8HapeSFW5jwdwcsjaDwEIAugZfRoz0N1vR/Q6T OFAYn2hRwlAgUXVk3NXpDNV/QRliGvxhLAVpvu1a4ExfVZoOQyPa8pogDgrUdB3e tYmmFHNa09Lv1nyMZWi6t7zH2weq6/Dxpm0BWf+THFcunv9TNfAqmDV5qbxvaUPh uvRpN+X2N3tejB8WKt+UmzAXUi3P3OgYimWXwq8Rmorc1rk5j+ksl6qYwZvi7rRV g1ZAH7bGhXC9eEU/1Z9q26OhAPdTyJD0pc+G9vMz6VijLRXcgHBUP09lSeqxnNxc pWoX6nRdGn6PkDhewHM05iqAE3ZHnc8kSBcRX85SoW5dGOhvvUTs4ePVNTo3vHdQ vftTDD+I3rbFnYTKUAzHTPSWGE7LVEiWJ94RKSADXgve0qq8o377UMnY7W3UygSY odyUZ29B5EfZ88EpIs/h5NomDv5VcQEoCWN1owIDAQABozYwNDAdBgNVHQ4EFgQU g1V3LV4h8UkMCSTnVAkSjch+BK4wEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZI hvcNAQELBQADggGBACn0LsqO94tk8i+RbG5hryNduem9n8b8doYD97iaux6QLvY/ A8DFduJtUevZ3OCsRYQSGa3V/ysMzN7/DIUkpRLevZmdw+1L6PGR7peR2xIQ+yEW bL88vLjezaYIzMKHJRmN8oP3DQtGJm6U2fMMiEHWqRtULIVpnFppzPI2z7+tDeyg PFD2YeiFWoq5lmXStrK+KYPJbhTn0gz4QlGBs7PLY2JMDRSVj6ctkvrpXbC3Rb3m qo2FY/y51ACg77Txc6NAmNE6tCknwaUjRQP2MuoYFm5/Z6O9/g49AEVIE101zHqV N6SkcTUaXAuQIyZaqwdndfOB4rrFyAkoxTM5OamIQl80hZKf4R5rM7D7Sz8kAWJi BPIcewN0XnI6lm+zPAVUAE8dZfgJmJR5ifZHYCuv96EX0RpYsddeik8UmjkZ2/ch vRzvRSNNxVC6Zoe6vKNUb89XMtJZqY80WxfWG3Z2Hwf9KvS+2KAH/6MiSMj0RI5F SCB2PMQm6DYXwM1EyA== -----END CERTIFICATE----- """ server_key_pem = normalize_privatekey_pem( b"""-----BEGIN RSA PRIVATE KEY----- MIIG5AIBAAKCAYEApT23GEqDo1zR1Ur1W/gxi5g/oeyhHjzf7ALndXxrGR/LZPKe sFK6Dwdql5IVbmPB3ByyNoPAQgC6Bl9GjPQ3W9H9DpM4UBifaFHCUCBRdWTc1ekM 1X9BGWIa/GEsBWm+7VrgTF9Vmg5DI9rymiAOCtR0Hd61iaYUc1rT0u/WfIxlaLq3 vMfbB6rr8PGmbQFZ/5McVy6e/1M18CqYNXmpvG9pQ+G69Gk35fY3e16MHxYq35Sb MBdSLc/c6BiKZZfCrxGaitzWuTmP6SyXqpjBm+LutFWDVkAftsaFcL14RT/Vn2rb o6EA91PIkPSlz4b28zPpWKMtFdyAcFQ/T2VJ6rGc3FylahfqdF0afo+QOF7AczTm KoATdkedzyRIFxFfzlKhbl0Y6G+9ROzh49U1Oje8d1C9+1MMP4jetsWdhMpQDMdM 9JYYTstUSJYn3hEpIANeC97SqryjfvtQydjtbdTKBJih3JRnb0HkR9nzwSkiz+Hk 2iYO/lVxASgJY3WjAgMBAAECggGAJST2X5OAe9yFnri25vGn0YVr6G5U2YM9osQU W6iYOpGXGx4e5evyvyYfo+rGvoXWMjCRLwf2099t8bjBFzZeq1lM1VXqtraSPtUC JRjettDxg3Rb2jI85APVpR4C00SuEpT3DrPvfi3ukcTJ/DNwdKbFY2GI1WRr/HJS Y3xebqjwstYmL12Nsu+NEiCAFMjU/kqHeGGWhDakTVSF2p96tE0nEIdRi1eLpTnv xt++B87n3FJ/gBP9+SZcth+uHKA8Wr42CqJR3z8b/blICYCd2LABFdZjL4aHfce9 Xe7UyVoySYC6N0YSbLLfsVu/w/qsYitcTvWCyekX4eT2U9Sdje46LGN4MFJSYy8K Qw4hzz6JhUrAiwxPb2MLkq6q7AvdFwVAFl7xuH9J13yuN9x+w4NL9h3hzr4iC7nk xVrmme279h1hfuCR1+1Bb0fLvdl5VevT9SZYCg5BCL7JxHGofcBZ3ZE9R9Q7QYVv rCKHFZ5tIOkVJk2mcR5NvK6r7ethAoHBAM7BFvBPHgJ5xtny7M9xvaMQD9PZ3zzb PUD83lh+DlmLyzKLw2/OblyJgO8ECWUDNR1QkL5khq5Z2t1Kj77Hak7mUUlICbIc LKZLiAosuKBo/ps6emRRhIf9NNYR2G1k9GWyk3KicD/htllPl10j64vgBg2M/LQJ 2Oh95oWMck7RRdWHCwfBjND3YsYoN0hY9GXgr+ByDRQgAacvnpHlFCRmSPqiAJGh kPKIRfjLgVFbL1cIj7oHpcModgZr7Dgc/wKBwQDMmVhsmiefTscZSCoCIqXVsJJ0 edDmIvAl3cFozf9/+5JADjnp/9zcdANNN/oMfynOPx+0R2CygxooZaRKbnHPcVlu SCxwaloagNSFVt8lZ2PwybutfdMN8YbU431ypNLJjInI3Z66eHBRDZZZviu5AtoL 5WYAvFzN502P1IVrJBo0lht7ftQMwM4NAhRaaFrUCrycREwUl0u9PxswmDhignWs +fyJ93D5aVC1wHjUN9WYTEOM66goZTuSDD8mE10CgcAbl3UiOMy+c9XvvBWSUZGH M1uJYCgEjRWNmLFridcMaDWD11cLkrbzrn4AZ7+BNX5fHSNT5UJ7/g3RPmQUh7RO Nzpd1zlEBbKHtsi+4tz4u0pPGOzAeoh/RXFJqDQD1VcwQzaeM8NbIxocrRx8F5EV p53nLQuEU1QZIsQiym1uy0rQhicYr+HE+V67Jx7JjuV+uw99mnrYVrUhxJ8axUF8 4hGXMQt2Y+NeGoWMAEyPuOWGbeQQZXjfpISrsrdhfa0CgcEAxqbdRBUpA3Tpu5Jl t00M1z5p9M2SFuE1ao61i5z3xrvsdGVbtefH+gRqcD85eYi+fpKrpc7oBGtmqnKF 4f76YgAcZQeOnlekxLbxocWHRDnuv4wfvYO9uHwZ/fojg3ylbSwXXABSbZsi8o/O u7P5n9k0/Pfu4igBs6oxlMU0BaM4DnbwmCe8m+VYKykpud440kjaeJ+XfyanU0hC jhw+Iueoehr/KLYn6wJmaxJGP0c3DHh/3gOxcgdYn6VkawPBAoHBAMJ7jfxZJfBO i0gDsD9Kz3EkGI8HbBpgC2Cd9DGQR9qTZy1/l/ObM2jwNumJjoHsN8fkha1d6/3n 01hA4LwLB/SLQHx+7k1749sH7m1FaczWa9eUxNkwFiVTBYIyvbekNfJogLX9pVow vEuNe+J8vxLt3gQJ1DUz+2Air8v//OIqQ+akDnPkwiqHDqynNNWO+jq708aUunVT TTvknsoT3qT8H/N1FwbCZ14eKV+bXHcv1lVrLdW/DnjDZRpMFa3LSg== -----END RSA PRIVATE KEY----- """ ) intermediate_server_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIEXTCCAsWgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQELBQAw ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh biBEaWVnbzAeFw0yMDA4MDIxNzExMjBaFw00NzEyMjAxNzExMjBaMG4xHTAbBgNV BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh biBEaWVnbzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAL3UcTxwCsMZ qIE+7lolm8t6lT0IYZkE4L7u2qI64m9CvztudqqKYZcrprZobZxqPhqc8IO3CFR2 nVzwZWxrHCcm6nAzJjVXUFrc4TLsVYYJL1QvKXxr97VIiySU7x6xWrQQsqDtlrb0 jH59EYFbM2eMk2fBT2X4h6YMXlqyrDjZF6apClXtkdxGJGqR5PCTs4cvrYW7TpIm cuJq0S+MRBguZpriM+wOK7cXrqfRPFRzZtPXskpQPSAMDDAOGKl8OZfoVFYzG8KG omOa0hcHtgYX2GCDs1g1maY6Haw9bgs041BoApH9aQxehy5dfU39DcFoKSE3dCjR FaR6ryCA+f8L1F3xVaHsvX443CYF0/holfsptTjNd1T1z8WR5h1jtY0gJ/ERgcJZ UgDRE3lEkTLExS/nuGVfdwnlkxny9jbtYp2YcjYjUkChLtTgz4ommeIdBdDvSu8M wWHMtQNxECs5qA5J384cLh11Nd9exWUjiQ9yAZ0qTOzTkdH7VPHfxQIDAQABMA0G CSqGSIb3DQEBCwUAA4IBgQA2jC5hJ/+46RLBuaZiJhBawiY+HqybEAZWM/IBGZO4 UKcRotovU+sb1jg+vpXwePSBPEtQoZce0jN0TKiCdlLM4/9lybAvc6qBLJ0d4VS5 BU5QsCs9IKyvswAFVipQZi0szYwHk8T145SH/fPao8oznf5ae4a6rK9PyZqT7Ix1 nnKGffbJs0dY+jlxmx/BPlbsGfTwPL6LexghjvbpbXWUdVLP3gAW6DPCtRd6lhWj JvgCkF2SnbQ7GgnPEYi8h09j0c6/sK6jLoNAatJyIlRGE1cdGYZVUlVW/xP6lYM0 Mi1KKl0ZXOne4vPTtnTBBqrpjdLydH3WM1IxdwSRbmF15OD6BWzzKV4IYUJ21GDh YrVrcIeN49pUoKVTTn0Sql8f8mXxJhJ54wo9TKdIGZeuwTZrfWjcjWghXgghXGoP RI/I5fk/OMu0Oc06/+xdwCBHCSge0/vxK6fhTu7PxmJhQcZF0sDZyb6LXm2feVkG 6FsxnsvstVNO3oJdpa8daLs= -----END CERTIFICATE----- """ intermediate_server_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- MIIG5AIBAAKCAYEAvdRxPHAKwxmogT7uWiWby3qVPQhhmQTgvu7aojrib0K/O252 qophlyumtmhtnGo+Gpzwg7cIVHadXPBlbGscJybqcDMmNVdQWtzhMuxVhgkvVC8p fGv3tUiLJJTvHrFatBCyoO2WtvSMfn0RgVszZ4yTZ8FPZfiHpgxeWrKsONkXpqkK Ve2R3EYkapHk8JOzhy+thbtOkiZy4mrRL4xEGC5mmuIz7A4rtxeup9E8VHNm09ey SlA9IAwMMA4YqXw5l+hUVjMbwoaiY5rSFwe2BhfYYIOzWDWZpjodrD1uCzTjUGgC kf1pDF6HLl19Tf0NwWgpITd0KNEVpHqvIID5/wvUXfFVoey9fjjcJgXT+GiV+ym1 OM13VPXPxZHmHWO1jSAn8RGBwllSANETeUSRMsTFL+e4ZV93CeWTGfL2Nu1inZhy NiNSQKEu1ODPiiaZ4h0F0O9K7wzBYcy1A3EQKzmoDknfzhwuHXU1317FZSOJD3IB nSpM7NOR0ftU8d/FAgMBAAECggGAYNwla1FALIzLDieuNxE5jXne7GV6Zzm187as mFqzb1H/gbO7mQlDAn+jcS+Xvlf3mFy73HloJrDfWqzPE6MTmmag+N8gf9ctiS9r OTCd8uZ839ews2vj2PxLAz97Q437WiWq/7I7VN8zUNdAN2DxucRg8nAQs1c8390v x9ejSN580u0t+OpfoqWnrzkCOD8lO7V4NOR+EtTLifw3AKvxkuUaNa12ENyqMaJD 3B1HS1AXB8DnmEOY7OE41sxaiSB44M7tsr31ldUCbEf/A5OZWeCfloP2c2g+Td8s +sl+AzoGa1HsFOqiqdDw8lKynfT1VukaaCtOr0pGeh6XW65aHRGI0B+mHIEM7yR0 f2NjfvgejqNekWyJ+XeTcmrPPcSH72F9ansLRpUullAi+6OkPFIiwyKCP/S2sLwh cqe3NITfMweWDt7GqgOhz1yWaewXgdruWiFAYAh2JDBtgMWTUwWgkKyFCb4mrI6r zqiBpA8Mjm/H17h/dQqF3iRuaZOBAoHBAPDvVseeiGwZjDXuQD9acCBZU23xwevR 6NVe/FLY1bybgsOBQCApQIWKH72eIHo12ULRMe/uZUo3su9JSCc8Gt8DsQpiZ2a+ z8rS6uEw/UZGMWeLjcIVK5IeeD7OJ/BXEbwoxVvWLYYgWHpYwY9eqppsMlVqmIHY lfRAaepEkU/4euRl1VTFxkU0sYw7Tj+gbFQDydB0NSLIU/x10tlHblT+O5tgBLJh kL7II9tyoGaCUjNnACErmi1FA+lNsx1eAwKBwQDJsw+sIhujRHrajCV5dqq5cx3h ZQNfamoX6xfXYjNHjkeFnFpHB2w6ffe00q2Kt5+0AaSA295n1vPx6IKzKYMr8kpD 0Kiv+mlKK5w7lZzdCeoJb8Co2t9viZXrN9lNetXiSZldrg5nlG8Gmi2RKn65vIfp ZFc8CExXpQWNMSLJlu2qM8Sjt4h8M880khuTggCeIDbw7YfyanlNhsNpOGv/r+Hd 3i0BP0Qd1sZWkZ+hp/JJFdvyEh5vINgSABfNJJcCgcEA8LqioVcEBcZM8oG3jdVF 3PyDQIHieUXFdpOuVvSyMf3LXJ3ivX+aKRNF/YZl+tWc24b7dzhh2hLm5PD6d8E1 NAiTNsX1fJJAOe4dopz5IuL1b/jezcGrRBbPnCkNfLTyUmcGMmlAGRhubugJlb9H hH2AmRmlgW8u/NnzOZADBL1HxLb+vPHS1cj9cRi8aRRXyGX0miPSB4vTZpcu8cvO MHvIgMkiSDz1i7mbIiNYorOpgBR066+OH5cqfkwVH82TAoHAO3dZdYyQzXARMIIF QmxkJUz1UFCxz93V7btYSh4ftEcUeyX/z9U2aYBeGafLloxQv4eEcqFgTwkm3vmI Hz5r9/b1Qk0wjsGrbTyyUTbpCpozsBiMmrv9CCtuUe0jWh6PFKpSVzZL9OnkWfP2 30fCGQymnX8B4ScpKuXyXxBPi1O+OmIM5Z/k04mK25sAGltHx1cEG8BMRoJxxROo ZUtHPBkk5H7ukeGPOaTq0PcaM1UKr9WMBTCmXGk4iwYP/mF9AoHBAOTlFVgGbbjk Cp/Wd7IrYCBKlnkIyBUMx5icLcsFmgXWx+Gx1HualD2aZ7kctYOfo+zLEyA6roni bSFLrxT4Od4uqwb51iZoJWxO+C3H1i9NoieU5JOnw5Osyl7OMXm3DkaS/N+ipP/b 3bx1y8/WnGgqWWguXKt2lmgOItaEKrXYr6VZ1Z4upnLtkbxLANnqkQcL9287tXaW GPVXEteEXrtPj1f+9QYsMKuTWfaw6XfnBkBHxEZgWR+2hAN2z3c/Eg== -----END RSA PRIVATE KEY----- """ client_cert_pem = b"""-----BEGIN CERTIFICATE----- MIIEJzCCAo+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHQ2hpY2FnbzEQMA4GA1UECgwH VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTIwMDgwMjE3MTEy MVoXDTQ3MTIyMDE3MTEyMVowFjEUMBIGA1UEAwwLdWdseSBjbGllbnQwggGiMA0G CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDGChdOMY164FScJqfiJ5LEtjYOKEg4 nmMAMGuHIT8wZZEfzaaHhBbypzKq2cPP1qtyHgvtUMM6KOFEj4y9AonqzzdlVxbM i6+AvYLWlPoB5r/G1GdslUvXbc7F02B/6sB/+iFXmcdjOjAQcLWxVgUL+1CoeoY1 awNYmzQueK/T82a/6AYTdrx7XRX4wfxjYb1o3bnnRD/jGSakIylXeUGFsiSNkBs/ dJMkUONxizAdAE2tW6NhPuE2O0UipzUhdgFnH6WPfJ0J1S7jZ3eQTUrLkFpWSp/Z hx/l/Ql9vO0wagHaT2wOiZdKVT8S6V6OPzJ7/H1evCoM6EuSPBC5DDP1nPetCK1v uC9kb7Dg6yFPt1CKrVFt0Y6W5Y5/GzisUtvYV/OGtX4DOwL9It68D04Qrvun1t/a Dh/c5gKqfqIGHUvUucFmOi6DrRpadfraLZMRGN2ysPjoVwhMgwwSmSWhziQIUfxK oyz1CUsyr5Gh5gdifbe1AOYwu6YdtlmhqCsCAwEAAaM2MDQwHQYDVR0OBBYEFINV dy1eIfFJDAkk51QJEo3IfgSuMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3 DQEBCwUAA4IBgQAhAEACc1j6EYoSfVJD8N/FlYfHRizdfVJyrmMnC8ID1vtfrU2z S2q+49ja2NyM4Sq+Cf+i+sFfzFG92LayZt9Mc1BnHZMdNzQL7Ynr2nDLxHsHzuYa N21/ucTpHEFGLmvQ/eWBMxQQ9TbiNXn+tnnqg46dRzN3vHJp+g5+ijtMcuh007z2 niiO8F07wlb960XviejWejMC8hBLWlA7i3EjAkDO8RFQnG2Py5cQX9GgmWH1sDy3 rIsWlU+e46ysSWK/bnudnAlzZMB9KJATVZu5+xmCumH2hLJv5vz+jnKcgU9MBZMO cKgNdFUbtRlU/gfTaohmLIuSquunCCrXLsLD8ygbKKXfSPGVo2XkvX3oxqUo6dmA LvU4N4sCQGiSzW+a13HBtk3TBZFsJSWUGSW/H7TVFiAonumJKRqRxMOkkB9JxX+V 9LZBYuBLpOeK4wZ8BUSNlHKnGpDzl0DzdYrGlzWz0jXlLGZ8KMfXAn9h0mOZ+IyK eUlgMBYyAspCQzM= -----END CERTIFICATE----- """ client_key_pem = normalize_privatekey_pem( b"""-----BEGIN RSA PRIVATE KEY----- MIIG5AIBAAKCAYEAxgoXTjGNeuBUnCan4ieSxLY2DihIOJ5jADBrhyE/MGWRH82m h4QW8qcyqtnDz9arch4L7VDDOijhRI+MvQKJ6s83ZVcWzIuvgL2C1pT6Aea/xtRn bJVL123OxdNgf+rAf/ohV5nHYzowEHC1sVYFC/tQqHqGNWsDWJs0Lniv0/Nmv+gG E3a8e10V+MH8Y2G9aN2550Q/4xkmpCMpV3lBhbIkjZAbP3STJFDjcYswHQBNrVuj YT7hNjtFIqc1IXYBZx+lj3ydCdUu42d3kE1Ky5BaVkqf2Ycf5f0JfbztMGoB2k9s DomXSlU/Eulejj8ye/x9XrwqDOhLkjwQuQwz9Zz3rQitb7gvZG+w4OshT7dQiq1R bdGOluWOfxs4rFLb2FfzhrV+AzsC/SLevA9OEK77p9bf2g4f3OYCqn6iBh1L1LnB Zjoug60aWnX62i2TERjdsrD46FcITIMMEpkloc4kCFH8SqMs9QlLMq+RoeYHYn23 tQDmMLumHbZZoagrAgMBAAECggGAAXA5UxwRBv9yHeA5/+6BpmQcaGXqgF7GIU44 ubaIGvXh4/U+bGWNNR35xDvorC3G+QE23PZlNJrvZ+wS/ZxzG/19TYMga0Podmrp 9F0Io9LlObB5P9SlxF7LzawHW2Z9F3DdpSE8zX+ysavf5fXV+4xLva2GJAUu9QnL izrdLBDsgiBRSvrly4+VhUUDbEVddtGFdCSOwjuAiFipCDWdQDdXBKAzUnaqSu07 eaulIdDKv6OWwDIQuLAdhG7qd9+/h5MB/rAG8v4bgbHz1H/RZw5VIOuOhfCodzJx 3Smfh5td21jwJ2RfZYEPNOMtFa9eRFtH2/uRa5jbJiZb8YWIzWy0xCNQpKheSoBO wiuMDBS2HCYm2SgEYDdJiE2OkRAk0UwTiUmlmZd0a3NfJ/rfQE+JiDQ28Arj3EZl SY/V3KdviM4MbaoX7f9j9sjAe5Rk1M+yI8OsnM/hf77m0CSiJJpLpvgqhMjjT+NI aBm1FyTq6qu506d0YUZy+Wr2DRsBAoHBAPfshuOiDXo9UmJxM1mSJQ0rQlxWSWmX bIAsPrpKslTFYHk7xbcCbJCqMbHmCsyvYy3oW3SpJs6Vi2wQWuUQmsm0YC7qdkXF Fyo2f7vF7roQcXLxVmQRo0OxZ9JpLAZ9DKMEcNfYyUiQiqJmZuIyWKngqBl6OoL2 8EJSFjTY1tR/nDxGLpZSsqoZJWQGd9B2bK4y6NktDF1GkexCpKaSyXZT612JGPG2 0gSIwRq1OgZH3SPHevhVMjJtxGue2XARywKBwQDMfZHtdJI9RuurM9UuULZ72SmW oLzki3LwFQ/QoS9wrHK+OqQDWH2ddON1PoB4LOCpwB4CC83pMyfxurgLHut6saHL hQ5N+h0jUC2pSJOXZSfF2Hx8YHCT7Dga5kmgEy89c1TF48IL2LdUZQQIGZt8+FxM 4nxT9NFlu/UWY2oftT+ZwFsIock/DYYUKxDXw9YkOmt1lO5u1SKte0NdQ4RhBeqK nRADMSS9oKZkSUxkwaDJH2GkUVTyBsF/kmh+dyECgcEA6jy3yRQPxcFwOAAZ8vOo PAP2I8WGgNQHOCYVce8nA/6jwocdq2YH6rpST3E4HOFMRFB3MAas2pvh6UyehDOm +xGHmmv9KLgoxcJN9rvwbC0i8uVfqRYc+dUAcYTaiprVOKP2dYilzAB8ayly5R2K NZ5DVCbuZ1Ql9ZMW1gFVH9odY7kvROmHUjyF3jZaN0PcNM12v9HXD72gGudwJs0i uMBa7LmeLql7TbtjLvewhcSaA7bx0PS1g33ACapAZ6j3AoHAN2PsGz3wPtjvDTjF Df6e730rXrm7cMy1HYMW/ZQrnYGYsx5/PsjBfd0jn6aGdgbx9AkuF6/K3tgUgc3p /Fkrv9hN0yr/bO/K5L3bIHegQuoLk/PIBIi69daOe/rVBp8rtKGA3PmMnljdj+as 6OTG0VsU5V6T/snZzozTHnVfUaduyt7nybbJJGMtZlkj/s31O2r3oKnuy+a/te4l mSWovf80QMe6hqLRKOxTJecU4lXwj4oIkNHXCJf74epuk5MBAoHBALyvg90KzMFX ZEjdPIXULR6/3rub8yD7LVYbNhhYWGo8GybzsBUC0kczRpRXFnmbq1GDIXQf5A+2 3ZaGsWzAxLjvL3KwH1LUaXVWwFMOM2n6zTk18XEXrNvp+E5QtPwpO5c4VlPr0cAC tTPAmbu6kVPlQ6mKiqlPAsfh0BD2mRVo2cTjZgDotKshb5uCHD8/PnCfOjCXFxOf DWjBuR73/r5Bj+ktRoD4V2SFdO6loJwH6B8rsBjD0NbAGs9otKvy+Q== -----END RSA PRIVATE KEY----- """ ) cleartextCertificateRequestPEM = b"""-----BEGIN CERTIFICATE REQUEST----- MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH EwdDaGljYWdvMRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEXMBUGA1UEAxMORnJl ZGVyaWNrIERlYW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSw BsUWkXdqg6tnXy8H8hA1msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nA E0zhmHJELcM8gUTIlXv/cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXN xQn5ecR0UYSOWj6TTGXB9VyUMQzCClcBAgMBAAGgADANBgkqhkiG9w0BAQUFAAOB gQAAJGuF/R/GGbeC7FbFW+aJgr9ee0Xbl6nlhu7pTe67k+iiKT2dsl2ti68MVTnu Vrb3HUNqOkiwsJf6kCtq5oPn3QVYzTa76Dt2y3Rtzv6boRSlmlfrgS92GNma8JfR oICQk3nAudi6zl1Dix3BCv1pUp5KMtGn3MeDEi6QFGy2rA== -----END CERTIFICATE REQUEST----- """ encryptedPrivateKeyPEM = b"""-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,9573604A18579E9E SHOho56WxDkT0ht10UTeKc0F5u8cqIa01kzFAmETw0MAs8ezYtK15NPdCXUm3X/2 a17G7LSF5bkxOgZ7vpXyMzun/owrj7CzvLxyncyEFZWvtvzaAhPhvTJtTIB3kf8B 8+qRcpTGK7NgXEgYBW5bj1y4qZkD4zCL9o9NQzsKI3Ie8i0239jsDOWR38AxjXBH mGwAQ4Z6ZN5dnmM4fhMIWsmFf19sNyAML4gHenQCHhmXbjXeVq47aC2ProInJbrm +00TcisbAQ40V9aehVbcDKtS4ZbMVDwncAjpXpcncC54G76N6j7F7wL7L/FuXa3A fvSVy9n2VfF/pJ3kYSflLHH2G/DFxjF7dl0GxhKPxJjp3IJi9VtuvmN9R2jZWLQF tfC8dXgy/P9CfFQhlinqBTEwgH0oZ/d4k4NVFDSdEMaSdmBAjlHpc+Vfdty3HVnV rKXj//wslsFNm9kIwJGIgKUa/n2jsOiydrsk1mgH7SmNCb3YHgZhbbnq0qLat/HC gHDt3FHpNQ31QzzL3yrenFB2L9osIsnRsDTPFNi4RX4SpDgNroxOQmyzCCV6H+d4 o1mcnNiZSdxLZxVKccq0AfRpHqpPAFnJcQHP6xyT9MZp6fBa0XkxDnt9kNU8H3Qw 7SJWZ69VXjBUzMlQViLuaWMgTnL+ZVyFZf9hTF7U/ef4HMLMAVNdiaGG+G+AjCV/ MbzjS007Oe4qqBnCWaFPSnJX6uLApeTbqAxAeyCql56ULW5x6vDMNC3dwjvS/CEh 11n8RkgFIQA0AhuKSIg3CbuartRsJnWOLwgLTzsrKYL4yRog1RJrtw== -----END RSA PRIVATE KEY----- """ encryptedPrivateKeyPEMPassphrase = b"foobar" cleartextPrivateKeyPEM = """-----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMcRMugJ4kvkOEuT AvMFr9+3A6+HAB6nKYcXXZz93ube8rJpBZQEfWn73H10dQiQR/a+rhxYEeLy8dPc UkFcGR9miVkukJ59zex7iySJY76bdBD8gyx1LTKrkCstP2XHKEYqgbj+tm7VzJnY sQLqoaa5NeyWJnUC3MJympkAS7p3AgMBAAECgYAoBAcNqd75jnjaiETRgVUnTWzK PgMCJmwsob/JrSa/lhWHU6Exbe2f/mcGOQDFpesxaIcrX3DJBDkkc2d9h/vsfo5v JLk/rbHoItWxwuY5n5raAPeQPToKpTDxDrL6Ejhgcxd19wNht7/XSrYZ+dq3iU6G mOEvU2hrnfIW3kwVYQJBAP62G6R0gucNfaKGtHzfR3TN9G/DnCItchF+TxGTtpdh Cz32MG+7pirT/0xunekmUIp15QHdRy496sVxWTCooLkCQQDIEwXTAwhLNRGFEs5S jSkxNfTVeNiOzlG8jPBJJDAdlLt1gUqjZWnk9yU+itMSGi/6eeuH2n04FFk+SV/T 7ryvAkB0y0ZDk5VOozX/p2rtc2iNm77A3N4kIdiTQuq4sZXhNgN0pwWwxke8jbcb 8gEAnqwBwWt//locTxHu9TmjgT8pAkEAlbF16B0atXptM02QxT8MlN8z4gxaqu4/ RX2FwpOq1FcVsqMbvwj/o+ouGY8wwRiK0TMrQCf/DFhdNTcc1aqHzQJBAKWtq4LI uVZjCAuyrqEnt7R1bOiLrar+/ezJPY2z+f2rb1TGr31ztPeFvO3edLw+QdhzwJGp QKImYzqMe+zkIOQ= -----END PRIVATE KEY----- """ cleartextPublicKeyPEM = b"""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxszlc+b71LvlLS0ypt/l gT/JzSVJtnEqw9WUNGeiChywX2mmQLHEt7KP0JikqUFZOtPclNY823Q4pErMTSWC 90qlUxI47vNJbXGRfmO2q6Zfw6SE+E9iUb74xezbOJLjBuUIkQzEKEFV+8taiRV+ ceg1v01yCT2+OjhQW3cxG42zxyRFmqesbQAUWgS3uhPrUQqYQUEiTmVhh4FBUKZ5 XIneGUpX1S7mXRxTLH6YzRoGFqRoc9A0BBNcoXHTWnxV215k4TeHMFYE5RG0KYAS 8Xk5iKICEXwnZreIt3jyygqoOKsKZMK/Zl2VhMGhJR6HXRpQCyASzEG7bgtROLhL ywIDAQAB -----END PUBLIC KEY----- """ crlData = b"""\ -----BEGIN X509 CRL----- MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8 MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1 4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT 0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid vrzEeLDRiiPl92dyyWmu -----END X509 CRL----- """ # The signature on this CRL is invalid. crlDataUnsupportedExtension = b"""\ -----BEGIN X509 CRL----- MIIGRzCCBS8CAQEwDQYJKoZIhvcNAQELBQAwJzELMAkGA1UEBhMCVVMxGDAWBgNV BAMMD2NyeXB0b2dyYXBoeS5pbxgPMjAxNTAxMDEwMDAwMDBaGA8yMDE2MDEwMTAw MDAwMFowggTOMBQCAQAYDzIwMTUwMTAxMDAwMDAwWjByAgEBGA8yMDE1MDEwMTAw MDAwMFowXDAYBgNVHRgEERgPMjAxNTAxMDEwMDAwMDBaMDQGA1UdHQQtMCukKTAn MQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5LmlvMAoGA1UdFQQD CgEAMHICAQIYDzIwMTUwMTAxMDAwMDAwWjBcMBgGA1UdGAQRGA8yMDE1MDEwMTAw MDAwMFowNAYDVR0dBC0wK6QpMCcxCzAJBgNVBAYTAlVTMRgwFgYDVQQDDA9jcnlw dG9ncmFwaHkuaW8wCgYDVR0VBAMKAQEwcgIBAxgPMjAxNTAxMDEwMDAwMDBaMFww GAYDVR0YBBEYDzIwMTUwMTAxMDAwMDAwWjA0BgNVHR0ELTArpCkwJzELMAkGA1UE BhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeS5pbzAKBgNVHRUEAwoBAjByAgEE GA8yMDE1MDEwMTAwMDAwMFowXDAYBgNVHRgEERgPMjAxNTAxMDEwMDAwMDBaMDQG A1UdHQQtMCukKTAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5 LmlvMAoGA1UdFQQDCgEDMHICAQUYDzIwMTUwMTAxMDAwMDAwWjBcMBgGA1UdGAQR GA8yMDE1MDEwMTAwMDAwMFowNAYDVR0dBC0wK6QpMCcxCzAJBgNVBAYTAlVTMRgw FgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wCgYDVR0VBAMKAQQwcgIBBhgPMjAxNTAx MDEwMDAwMDBaMFwwGAYDVR0YBBEYDzIwMTUwMTAxMDAwMDAwWjA0BgNVHR0ELTAr pCkwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dyYXBoeS5pbzAKBgNV HRUEAwoBBTByAgEHGA8yMDE1MDEwMTAwMDAwMFowXDAYBgNVHRgEERgPMjAxNTAx MDEwMDAwMDBaMDQGA1UdHQQtMCukKTAnMQswCQYDVQQGEwJVUzEYMBYGA1UEAwwP Y3J5cHRvZ3JhcGh5LmlvMAoGA1UdFQQDCgEGMHICAQgYDzIwMTUwMTAxMDAwMDAw WjBcMBgGA1UdGAQRGA8yMDE1MDEwMTAwMDAwMFowNAYDVR0dBC0wK6QpMCcxCzAJ BgNVBAYTAlVTMRgwFgYDVQQDDA9jcnlwdG9ncmFwaHkuaW8wCgYDVR0VBAMKAQgw cgIBCRgPMjAxNTAxMDEwMDAwMDBaMFwwGAYDVR0YBBEYDzIwMTUwMTAxMDAwMDAw WjA0BgNVHR0ELTArpCkwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2NyeXB0b2dy YXBoeS5pbzAKBgNVHRUEAwoBCTByAgEKGA8yMDE1MDEwMTAwMDAwMFowXDAYBgNV HRgEERgPMjAxNTAxMDEwMDAwMDBaMDQGA1UdHQQtMCukKTAnMQswCQYDVQQGEwJV UzEYMBYGA1UEAwwPY3J5cHRvZ3JhcGh5LmlvMAoGA1UdFQQDCgEKMC4CAQsYDzIw MTUwMTAxMDAwMDAwWjAYMAoGA1UdFQQDCgEBMAoGAyoDBAQDCgEAMA0GCSqGSIb3 DQEBCwUAA4IBAQBTaloHlPaCZzYee8LxkWej5meiqxQVNWFoVdjesroa+f1FRrH+ drRU60Nq97KCKf7f9GNN/J3ZIlQmYhmuDqh12f+XLpotoj1ZRfBz2hjFCkJlv+2c oWWGNHgA70ndFoVtcmX088SYpX8E3ARATivS4q2h9WlwV6rO93mhg3HGIe3JpcK4 7BcW6Poi/ut/zsDOkVbI00SqaujRpdmdCTht82MH3ztjyDkI9KYaD/YEweKSrWOz SdEILd164bfBeLuplVI+xpmTEMVNpXBlSXl7+xIw9Vk7p7Q1Pa3k/SvhOldYCm6y C1xAg/AAq6w78yzYt18j5Mj0s6eeHi1YpHKw -----END X509 CRL----- """ # A broken RSA private key which can be used to test the error path through # PKey.check. inconsistentPrivateKeyPEM = b"""-----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh 5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEaAQJBAIqm/bz4NA1H++Vx5Ewx OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT zIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4 nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2 HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNeH+vRWsAYU/gbx+OQB+7VOcBAiEA oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w= -----END RSA PRIVATE KEY----- """ # certificate with NULL bytes in subjectAltName and common name nulbyteSubjectAltNamePEM = b"""-----BEGIN CERTIFICATE----- MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL 08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251 bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9 i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ= -----END CERTIFICATE-----""" large_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- MIIJYgIBAAKCAg4AtRua8eIeevRfsj+fkcHr1vmse7Kgb+oX1ssJAvCb1R7JQMnH hNDjDP6b3vEkZuPUzlDHymP+cNkXvvi4wJ4miVbO3+SeU4Sh+jmsHeHzGIXat9xW 9PFtuPM5FQq8zvkY8aDeRYmYwN9JKu4/neMBCBqostYlTEWg+bSytO/qWnyHTHKh g0GfaDdqUQPsGQw+J0MgaYIjQOCVASHAPlzbDQLCtuOb587rwTLkZA2GwoHB/LyJ BwT0HHgBaiObE12Vs6wi2en0Uu11CiwEuK1KIBcZ2XbE6eApaZa6VH9ysEmUxPt7 TqyZ4E2oMIYaLPNRxuvozdwTlj1svI1k1FrkaXGc5MTjbgigPMKjIb0T7b/4GNzt DhP1LvAeUMnrEi3hJJrcJPXHPqS8/RiytR9xQQW6Sdh4LaA3f9MQm3WSevWage3G P8YcCLssOVKsArDjuA52NF5LmYuAeUzXprm4ITDi2oO+0iFBpFW6VPEK4A9vO0Yk M/6Wt6tG8zyWhaSH1zFUTwfQ9Yvjyt5w1lrUaAJuoTpwbMVZaDJaEhjOaXU0dyPQ jOsePDOQcU6dkeTWsQ3LsHPEEug/X6819TLG5mb3V7bvV9nPFBfTJSCEG794kr90 XgZfIN71FrdByxLerlbuJI21pPs/nZi9SXi9jAWeiS45/azUxMsyYgJArui+gjq7 sV1pWiBm6/orAgMBAAECggINQp5L6Yu+oIXBqcSjgq8tfF9M5hd30pLuf/EheHZf LA7uAqn2fVGFI2OInIJhXIOT5OxsAXO0xXfltzawZxIFpOFMqajj4F7aYjvSpw9V J4EdSiJ/zgv8y1qUdbwEZbHVThRZjoSlrtSzilonBoHZAE0mHtqMz7iRFSk1zz6t GunRrvo/lROPentf3TsvHquVNUYI5yaapyO1S7xJhecMIIYSb8nbsHI54FBDGNas 6mFmpPwI/47/6HTwOEWupnn3NicsjrHzUInOUpaMig4cRR+aP5bjqg/ty8xI8AoN evEmCytiWTc+Rvbp1ieN+1jpjN18PjUk80/W7qioHUDt4ieLic8uxWH2VD9SCEnX Mpi9tA/FqoZ+2A/3m1OfrY6jiZVE2g+asi9lCK7QVWL39eK82H4rPvtp0/dyo1/i ZZz68TXg+m8IgEZcp88hngbkuoTTzpGE73QuPKhGA1uMIimDdqPPB5WP76q+03Oi IRR5DfZnqPERed49by0enJ7tKa/gFPZizOV8ALKr0Dp+vfAkxGDLPLBLd2A3//tw xg0Q/wltihHSBujv4nYlDXdc5oYyMYZ+Lhc/VuOghHfBq3tgEQ1ECM/ofqXEIdy7 nVcpZn3Eeq8Jl5CrqxE1ee3NxlzsJHn99yGQpr7mOhW/psJF3XNz80Meg3L4m1T8 sMBK0GbaassuJhdzb5whAoIBBw48sx1b1WR4XxQc5O/HjHva+l16i2pjUnOUTcDF RWmSbIhBm2QQ2rVhO8+fak0tkl6ZnMWW4i0U/X5LOEBbC7+IS8bO3j3Revi+Vw5x j96LMlIe9XEub5i/saEWgiz7maCvfzLFU08e1OpT4qPDpP293V400ubA6R7WQTCv pBkskGwHeu0l/TuKkVqBFFUTu7KEbps8Gjg7MkJaFriAOv1zis/umK8pVS3ZAM6e 8w5jfpRccn8Xzta2fRwTB5kCmfxdDsY0oYGxPLRAbW72bORoLGuyyPp/ojeGwoik JX9RttErc6FjyZtks370Pa8UL5QskyhMbDhrZW2jFD+RXYM1BrvmZRjbAoIBBwy4 iFJpuDfytJfz1MWtaL5DqEL/kmiZYAXl6hifNhGu5GAipVIIGsDqEYW4i+VC15aa 7kOCwz/I5zsB3vSDW96IRs4wXtqEZSibc2W/bqfVi+xcvPPl1ZhQ2EAwa4D/x035 kyf20ffWOU+1yf2cnijzqs3IzlveUm+meLw5s3Rc+iG7DPWWeCoe1hVwANI1euNc pqKwKY905yFyjOje2OgiEU2kS4YME4zGeBys8yo7E42hNnN2EPK6xkkUqzdudLLQ 8OUlKRTc8AbIf3XG1rpA4VUpTv3hhxGGwCRy6If8zgZQsNYchgNztRGk72Gcb8Dm vFSEN3ZtwxU64G3YZzntdcr2WPzxAoIBBw30g6Fgdb/gmVnOpL0//T0ePNDKIMPs jVJLaRduhoZgB1Bb9qPUPX0SzRzLZtg1tkZSDjBDoHmOHJfhxUaXt+FLCPPbrE4t +nq9n/nBaMM779w9ClqhqLOyGrwKoxjSmhi+TVEHyIxCbXMvPHVHfX9WzxjbcGrN ZvRaEVZWo+QlIX8yqdSwqxLk1WtAIRzvlcj7NKum8xBxPed6BNFep/PtgIAmoLT5 L8wb7EWb2iUdc2KbZ4OaY51lDScqpATgXu3WjXfM+Q52G0mX6Wyd0cjlL711Zrjb yLbiueZT94lgIHHRRKtKc8CEqcjkQV5OzABS3P/gQSfgZXBdLKjOpTnKDUq7IBeH AoIBBweAOEIAPLQg1QRUrr3xRrYKRwlakgZDii9wJt1l5AgBTICzbTA1vzDJ1JM5 AqSpCV6w9JWyYVcXK+HLdKBRZLaPPNEQDJ5lOxD6uMziWGl2rg8tj+1xNMWfxiPz aTCjoe4EoBUMoTq2gwzRcM2usEQNikXVhnj9Wzaivsaeb4bJ3GRPW5DkrO6JSEtT w+gvyMqQM2Hy5k7E7BT46sXVwaj/jZxuqGnebRixXtnp0WixdRIqYWUr1UqLf6hQ G7WP2BgoxCMaCmNW8+HMD/xuxucEotoIhZ+GgJKBFoNnjl3BX+qxYdSe9RbL/5Tr 4It6Jxtj8uETJXEbv9Cg6v1agWPS9YY8RLTBAoIBBwrU2AsAUts6h1LgGLKK3UWZ oLH5E+4o+7HqSGRcRodVeN9NBXIYdHHOLeEG6YNGJiJ3bFP5ZQEu9iDsyoFVKJ9O Mw/y6dKZuxOCZ+X8FopSROg3yWfdOpAm6cnQZp3WqLNX4n/Q6WvKojfyEiPphjwT 0ymrUJELXLWJmjUyPoAk6HgC0Gs28ZnEXbyhx7CSbZNFyCU/PNUDZwto3GisIPD3 le7YjqHugezmjMGlA0sDw5aCXjfbl74vowRFYMO6e3ItApfSRgNV86CDoX74WI/5 AYU/QVM4wGt8XGT2KwDFJaxYGKsGDMWmXY04dS+WPuetCbouWUusyFwRb9SzFave vYeU7Ab/ -----END RSA PRIVATE KEY-----""" ec_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgYirTZSx+5O8Y6tlG cka6W6btJiocdrdolfcukSoTEk+hRANCAAQkvPNu7Pa1GcsWU4v7ptNfqCJVq8Cx zo0MUVPQgwJ3aJtNM1QMOQUayCrRwfklg+D/rFSUwEUqtZh7fJDiFqz3 -----END PRIVATE KEY----- """ ec_public_key_pem = b"""-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJLzzbuz2tRnLFlOL+6bTX6giVavA sc6NDFFT0IMCd2ibTTNUDDkFGsgq0cH5JYPg/6xUlMBFKrWYe3yQ4has9w== -----END PUBLIC KEY----- """ ec_root_key_pem = b"""-----BEGIN EC PRIVATE KEY----- MIGlAgEBBDEAz/HOBFPYLB0jLWeTpJn4Yc4m/C4mdWymVHBjOmnwiPHKT326iYN/ ZhmSs+RM94RsoAcGBSuBBAAioWQDYgAEwE5vDdla/nLpWAPAQ0yFGqwLuw4BcN2r U+sKab5EAEHzLeceRa8ffncYdCXNoVsBcdob1y66CFZMEWLetPTmGapyWkBAs6/L 8kUlkU9OsE+7IVo4QQJkgV5gM+Dim1XE -----END EC PRIVATE KEY----- """ ec_root_cert_pem = b"""-----BEGIN CERTIFICATE----- MIICLTCCAbKgAwIBAgIMWW/hwTl6ufz6/WkCMAoGCCqGSM49BAMDMFgxGDAWBgNV BAMTD1Rlc3RpbmcgUm9vdCBDQTEQMA4GA1UEChMHVGVzdGluZzEQMA4GA1UEBxMH Q2hpY2FnbzELMAkGA1UECBMCSUwxCzAJBgNVBAYTAlVTMCAXDTE3MDcxOTIyNDgz M1oYDzk5OTkxMjMxMjM1OTU5WjBYMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0Ex EDAOBgNVBAoTB1Rlc3RpbmcxEDAOBgNVBAcTB0NoaWNhZ28xCzAJBgNVBAgTAklM MQswCQYDVQQGEwJVUzB2MBAGByqGSM49AgEGBSuBBAAiA2IABMBObw3ZWv5y6VgD wENMhRqsC7sOAXDdq1PrCmm+RABB8y3nHkWvH353GHQlzaFbAXHaG9cuughWTBFi 3rT05hmqclpAQLOvy/JFJZFPTrBPuyFaOEECZIFeYDPg4ptVxKNDMEEwDwYDVR0T AQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQAMB0GA1UdDgQWBBSoTrF0H2m8RDzB MnY2KReEPfz7ZjAKBggqhkjOPQQDAwNpADBmAjEA3+G1oVCxGjYX4iUN93QYcNHe e3fJQJwX9+KsHRut6qNZDUbvRbtO1YIAwB4UJZjwAjEAtXCPURS5A4McZHnSwgTi Td8GMrwKz0557OxxtKN6uVVy4ACFMqEw0zN/KJI1vxc9 -----END CERTIFICATE-----""" rsa_p_not_prime_pem = """ -----BEGIN RSA PRIVATE KEY----- MBsCAQACAS0CAQcCAQACAQ8CAQMCAQACAQACAQA= -----END RSA PRIVATE KEY----- """ dsa_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MIICZAIBADCCAjkGByqGSM44BAEwggIsAoIBAQD7UzdlshSCIIuntch43VmfCX1+ WQDTvGw83sRZcN+B7nwFn4dm2PU8cby17oCjX7buBvalVqofnUokrSIDA6Rozm/f 2wpGR9oVpd0xh9cI50pw1G3RZ4lcNWTP8C8O20eIzJoCH1KElcWLCHLAa3XoGOMv p4XnbVgMdc9/ydt4qttzIVPV4cZoVObzixoKCgwHyVPDxe0JaCe2cIwxyQY0IwAI PfaUWEAo+bf7pOosdnatJYm9MkKe8bEgKGQcUl9S8FXLhRejMo+oobcRjuBHTAmY fuV1iGlLrkFNrc2O6M1CRZhOoddoy53IeHcSjfzKET1biE3tCOUdHjUnABqfAiEA 1llvauVKMLvFCDatVKRY+zNGJaa5dwff4qDtodz6sa8CggEAd+btod0di21mqFaf vc1ddmLK74PddMseT8DmoN/YduJaGLAOOVJ61rdG+KPXIar+8X5yqXfzP0MiYGkE A+xpNIImC3rzHElYNa8imA7ud8f+oC5jQijp0GhzVIS4UW83rZwakX7LITNE9Oj9 FkETH1ZskHpp5BNlNoaSIW2+T7n/a+lq+tN60gP3f6FPBv5obB0pjqh+OAzEil/4 Ys0dtCB0022cCUCqThMhWewlE2W2JioDLV5QkD91NMQNQwljDONNcs94AaWeVONK RaBQXlFsJPHzS8uKpsFeusFTrHIeEJW/8GQp/tfXP1ajEdg5EGxOhXFkem4ZMIus YFTbWwQiAiBFtgi8aNV0Jz2o8T+cxjVqVEgGdYNQqmpzqqBsM5AEOw== -----END PRIVATE KEY----- """ dsa_public_key_pem = b"""-----BEGIN PUBLIC KEY----- MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQD7UzdlshSCIIuntch43VmfCX1+WQDT vGw83sRZcN+B7nwFn4dm2PU8cby17oCjX7buBvalVqofnUokrSIDA6Rozm/f2wpG R9oVpd0xh9cI50pw1G3RZ4lcNWTP8C8O20eIzJoCH1KElcWLCHLAa3XoGOMvp4Xn bVgMdc9/ydt4qttzIVPV4cZoVObzixoKCgwHyVPDxe0JaCe2cIwxyQY0IwAIPfaU WEAo+bf7pOosdnatJYm9MkKe8bEgKGQcUl9S8FXLhRejMo+oobcRjuBHTAmYfuV1 iGlLrkFNrc2O6M1CRZhOoddoy53IeHcSjfzKET1biE3tCOUdHjUnABqfAiEA1llv auVKMLvFCDatVKRY+zNGJaa5dwff4qDtodz6sa8CggEAd+btod0di21mqFafvc1d dmLK74PddMseT8DmoN/YduJaGLAOOVJ61rdG+KPXIar+8X5yqXfzP0MiYGkEA+xp NIImC3rzHElYNa8imA7ud8f+oC5jQijp0GhzVIS4UW83rZwakX7LITNE9Oj9FkET H1ZskHpp5BNlNoaSIW2+T7n/a+lq+tN60gP3f6FPBv5obB0pjqh+OAzEil/4Ys0d tCB0022cCUCqThMhWewlE2W2JioDLV5QkD91NMQNQwljDONNcs94AaWeVONKRaBQ XlFsJPHzS8uKpsFeusFTrHIeEJW/8GQp/tfXP1ajEdg5EGxOhXFkem4ZMIusYFTb WwOCAQUAAoIBAEe6z5ud1k4EDD9mLP7UYALWrgc1NXUlDynoYkjr+T/NVf1eaMdq 0vFbGcEmz05UPUNXOhDH0szUDxQam3IE9C27ZO4SOquc0/rIhPY6i75SJW13P+cg gdXhDMTW5JOlyV6CPUoCWKOtn1ds3pTDuuWlZ89UzOWQUbC1si6vvz43zDyhfu6U owgIusPxowErm2sH66+MPa8fYxVX7ZJL0mEfubejrloAbo5unYI/bUYIhx4mtpP/ h/isFRifEAwG3yX6F9X/ZOYL53Z93EFPLJGRGMmQbkmXRA6lyvHdsC+OC/OCvPjW WfTXW9NHtUqpEks+OXBkyV971Hk5NvdLLr8= -----END PUBLIC KEY----- """ ed25519_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIKlxBbhVsSURoLTmsu9uTqYH6oF7zpxmp1ZQCAPhDmI2 -----END PRIVATE KEY----- """ ed25519_public_key_pem = b"""-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAq+FrpdwI1oTPytx8kGzuLVc+78zJE7hjYG4E9hwXoKI= -----END PUBLIC KEY----- """ ed448_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOcqZ7a3k6JwrJbYO8CNTPT/d7dlWCo5vCf0EYDj79ZvA\nhD8u9EPHlYJw5Y8ZQdH4WmVEfpKA23xkdQ== -----END PRIVATE KEY----- """ ed448_public_key_pem = b"""-----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAKFfWGCuqIaxgR9GmEXLRciYDyEjTnF56kr0sOVfwHEj+bHSU\neMJTZJR8qFSg8hNsHY1iZh9PIXcA -----END PUBLIC KEY----- """ rsa_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MIIEuwIBADANBgkqhkiG9w0BAQEFAASCBKUwggShAgEAAoIBAQDZ5FaSaXKn/RTF xyNr+GRvYnMvLz5XxSDD4JzVRKXxKGFzKKXMJAeXJkvPlho7Ta/HgMNXhMPAe8TT wcIRnHJqAfmSOnka1ks3Kl6EGQBTevKzyJy8MaUhzZsL4FUUgWUETFQQT8Dwcghf JobV0k+bWT4mrKHzIquw5y+NTsaZl4jSB1labhImsU16Vj66fHp7w9+c501tOxQO M4CQNWioGm8tgPT/43QUs9e+L2HFBI+cDQbEC68l+7VM8YY8NZ/fGypoML2QMVnU Y6zneoOLJTMUulOubrL+J6DkuuhxBsIOcyxMnqwgKm4pUGlPxfPSS7+Mo3JC969k wgUHerXZAgMBAAECgf9qAzz/VMCQwnV1UxkhxH/8zgYgYL+fERFuPC/ZWv7wOicv xAjm9KC8zVb44fLE586CCc7IN+zNK9y0gB9eAGr/04RhEvWgbmoqF6wdtdNyynuE Ut4oQKn7AUc1uPAeCfM4slw0Pie98YSS/9ZhwH/eh3C10iwWA1aiLWeDrnryPuJN mNB0d/ZsaL+arhR/nU2sJixx5LDI6AG0GJrw3DBHEKb4vZPIUM3wZNs7qnuG5W17 JbZDQYnkApByZu2UMWI2YUkpJC246mFPWSWMa6sAl7sTWTkUIR21lJiqyTGG3ljY C2QjHoHrrzs+pwtlLBa1a4FgbaJmnL+VzWD/FQECgYEA8r3Y2oGcY5cQPb00TE0t ekXAXiHz9sX76nzE6BMZ8cwP/cVoWtIABpdaimKUoFML8CdjOi9Ti9OoNVGWm4Pk fT/GOUdysXWIw2Z/VOLM47nDwJb3fWwxsxph+x3gWJG/Vct/1NxmCCEendM63dy7 /uR8RgX+0nxvn6Y6auQfpnkCgYEA5csHboa14Favx8aHTlITWOm46ugzdbARdfWz 13Ewb7m4mm/3gKtA/m+yGdQFwmtBVkmwtdCeDj0aKH3Sfvg9WCQK1x/dUkPMr//r oGUGeJU9r3ZKVJTeSJ0lKX4h3u3+1TdpnAgtuWGI4AK9fEdulfHKArxyIdbsdwRr ljaBMmECgYATpEcCz1APQu7+f+vWbLxMU46QT2EFS9npjHUGbl1AEooMt8eM6cc0 wVSDNBzgqDekFBvUXnX9L4BB6DsulEqN0/Y/NkfSkjch0I5nGP8JQkPTtqOKE5Il 8vGQt0crA4ge8huC5t6es8ddb/UodK8FnglsRRnsgEMsAPBjK9hfyQKBgDHD23Mr R14zR9Q7AXiLu9bonvx4lxRosg9ay7zfrX60uO7xSqeZ7vRrWiXPzgOB2N+IC/YE HQa2YuDcBucqeZaKD7LxGqxDNKP1B6Fv34vjvj0uoABbURxms/Kdd1ZhMmwYmQ2K k+Ru5AancUPl8GQWvgoDp6/+bK2Fzor0eNxhAoGBANcJ6mGvgw3px/H2MPBjRBsf tUbZ39UH3c4siLa2Rry/Pm0Fgly8CUmu1IcFQDITKbyhaGPuHGtXglBOZqXid0VL 01ReWISyKwWyuRjUuscdq2m684hXHYZCq2eJroqon1nMq4C0aqr696ra0cgCfbK3 5yscAByxKd+64JZziDkZ -----END PRIVATE KEY----- """ rsa_public_key_pem = b"""-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2eRWkmlyp/0Uxccja/hk b2JzLy8+V8Ugw+Cc1USl8ShhcyilzCQHlyZLz5YaO02vx4DDV4TDwHvE08HCEZxy agH5kjp5GtZLNypehBkAU3rys8icvDGlIc2bC+BVFIFlBExUEE/A8HIIXyaG1dJP m1k+Jqyh8yKrsOcvjU7GmZeI0gdZWm4SJrFNelY+unx6e8PfnOdNbTsUDjOAkDVo qBpvLYD0/+N0FLPXvi9hxQSPnA0GxAuvJfu1TPGGPDWf3xsqaDC9kDFZ1GOs53qD iyUzFLpTrm6y/ieg5LrocQbCDnMsTJ6sICpuKVBpT8Xz0ku/jKNyQvevZMIFB3q1 2QIDAQAB -----END PUBLIC KEY----- """ x25519_private_key_pem = b"""-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VuBCIEIPAjVfPNTm25VxtBRg+JjjFx9tA3M8aaBdVhjb92iBts -----END PRIVATE KEY----- """ @pytest.fixture def x509_data(): """ Create a new private key and start a certificate request (for a test to finish in one way or another). """ # Basic setup stuff to generate a certificate pkey = PKey() pkey.generate_key(TYPE_RSA, 2048) req = X509Req() req.set_pubkey(pkey) # Authority good you have. req.get_subject().commonName = "Yoda root CA" x509 = X509() subject = x509.get_subject() subject.commonName = req.get_subject().commonName x509.set_issuer(subject) x509.set_pubkey(pkey) now = datetime.now() expire = datetime.now() + timedelta(days=100) x509.set_notBefore(now.strftime("%Y%m%d%H%M%SZ").encode()) x509.set_notAfter(expire.strftime("%Y%m%d%H%M%SZ").encode()) yield pkey, x509 class TestX509Ext: """ Tests for `OpenSSL.crypto.X509Extension`. """ def test_str(self): """ The string representation of `X509Extension` instances as returned by `str` includes stuff. """ # This isn't necessarily the best string representation. Perhaps it # will be changed/improved in the future. assert ( str(X509Extension(b"basicConstraints", True, b"CA:false")) == "CA:FALSE" ) def test_type(self): """ `X509Extension` can be used to create instances of that type. """ assert is_consistent_type( X509Extension, "X509Extension", b"basicConstraints", True, b"CA:true", ) def test_construction(self): """ `X509Extension` accepts an extension type name, a critical flag, and an extension value and returns an `X509Extension` instance. """ basic = X509Extension(b"basicConstraints", True, b"CA:true") assert isinstance(basic, X509Extension) comment = X509Extension(b"nsComment", False, b"pyOpenSSL unit test") assert isinstance(comment, X509Extension) @pytest.mark.parametrize( "type_name, critical, value", [ (b"thisIsMadeUp", False, b"hi"), (b"basicConstraints", False, b"blah blah"), # Exercise a weird one (an extension which uses the r2i method). # This exercises the codepath that requires a non-NULL ctx to be # passed to X509V3_EXT_nconf. It can't work now because we provide # no configuration database. It might be made to work in the # future. ( b"proxyCertInfo", True, b"language:id-ppl-anyLanguage,pathlen:1,policy:text:AB", ), ], ) def test_invalid_extension(self, type_name, critical, value): """ `X509Extension` raises something if it is passed a bad extension name or value. """ with pytest.raises(Error): X509Extension(type_name, critical, value) @pytest.mark.parametrize("critical_flag", [True, False]) def test_get_critical(self, critical_flag): """ `X509ExtensionType.get_critical` returns the value of the extension's critical flag. """ ext = X509Extension(b"basicConstraints", critical_flag, b"CA:true") assert ext.get_critical() == critical_flag @pytest.mark.parametrize( "short_name, value", [(b"basicConstraints", b"CA:true"), (b"nsComment", b"foo bar")], ) def test_get_short_name(self, short_name, value): """ `X509ExtensionType.get_short_name` returns a string giving the short type name of the extension. """ ext = X509Extension(short_name, True, value) assert ext.get_short_name() == short_name def test_get_data(self): """ `X509Extension.get_data` returns a string giving the data of the extension. """ ext = X509Extension(b"basicConstraints", True, b"CA:true") # Expect to get back the DER encoded form of CA:true. assert ext.get_data() == b"0\x03\x01\x01\xff" def test_unused_subject(self, x509_data): """ The `subject` parameter to `X509Extension` may be provided for an extension which does not use it and is ignored in this case. """ pkey, x509 = x509_data ext1 = X509Extension( b"basicConstraints", False, b"CA:TRUE", subject=x509 ) x509.add_extensions([ext1]) x509.sign(pkey, "sha256") # This is a little lame. Can we think of a better way? text = dump_certificate(FILETYPE_TEXT, x509) assert b"X509v3 Basic Constraints:" in text assert b"CA:TRUE" in text def test_subject(self, x509_data): """ If an extension requires a subject, the `subject` parameter to `X509Extension` provides its value. """ pkey, x509 = x509_data ext3 = X509Extension( b"subjectKeyIdentifier", False, b"hash", subject=x509 ) x509.add_extensions([ext3]) x509.sign(pkey, "sha256") text = dump_certificate(FILETYPE_TEXT, x509) assert b"X509v3 Subject Key Identifier:" in text def test_missing_subject(self): """ If an extension requires a subject and the `subject` parameter is given no value, something happens. """ with pytest.raises(Error): X509Extension(b"subjectKeyIdentifier", False, b"hash") @pytest.mark.parametrize("bad_obj", [True, object(), "hello", []]) def test_invalid_subject(self, bad_obj): """ If the `subject` parameter is given a value which is not an `X509` instance, `TypeError` is raised. """ with pytest.raises(TypeError): X509Extension( "basicConstraints", False, "CA:TRUE", subject=bad_obj ) def test_unused_issuer(self, x509_data): """ The `issuer` parameter to `X509Extension` may be provided for an extension which does not use it and is ignored in this case. """ pkey, x509 = x509_data ext1 = X509Extension( b"basicConstraints", False, b"CA:TRUE", issuer=x509 ) x509.add_extensions([ext1]) x509.sign(pkey, "sha256") text = dump_certificate(FILETYPE_TEXT, x509) assert b"X509v3 Basic Constraints:" in text assert b"CA:TRUE" in text def test_issuer(self, x509_data): """ If an extension requires an issuer, the `issuer` parameter to `X509Extension` provides its value. """ pkey, x509 = x509_data ext2 = X509Extension( b"authorityKeyIdentifier", False, b"issuer:always", issuer=x509 ) x509.add_extensions([ext2]) x509.sign(pkey, "sha256") text = dump_certificate(FILETYPE_TEXT, x509) assert b"X509v3 Authority Key Identifier:" in text assert b"DirName:/CN=Yoda root CA" in text def test_missing_issuer(self): """ If an extension requires an issue and the `issuer` parameter is given no value, something happens. """ with pytest.raises(Error): X509Extension( b"authorityKeyIdentifier", False, b"keyid:always,issuer:always" ) @pytest.mark.parametrize("bad_obj", [True, object(), "hello", []]) def test_invalid_issuer(self, bad_obj): """ If the `issuer` parameter is given a value which is not an `X509` instance, `TypeError` is raised. """ with pytest.raises(TypeError): X509Extension( "basicConstraints", False, "keyid:always,issuer:always", issuer=bad_obj, ) class TestPKey: """ Tests for `OpenSSL.crypto.PKey`. """ @pytest.mark.parametrize( ("key_string", "key_type"), [ (dsa_private_key_pem, dsa.DSAPrivateKey), (ec_private_key_pem, ec.EllipticCurvePrivateKey), (ed25519_private_key_pem, ed25519.Ed25519PrivateKey), (ed448_private_key_pem, ed448.Ed448PrivateKey), (rsa_private_key_pem, rsa.RSAPrivateKey), ], ) def test_convert_roundtrip_cryptography_private_key( self, key_string, key_type ): """ PKey.from_cryptography_key creates a proper private PKey. PKey.to_cryptography_key creates a proper cryptography private key. """ key = serialization.load_pem_private_key(key_string, None) assert isinstance(key, key_type) pkey = PKey.from_cryptography_key(key) assert isinstance(pkey, PKey) parsed_key = pkey.to_cryptography_key() assert isinstance(parsed_key, key_type) assert parsed_key.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) == key.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert pkey._only_public is False assert pkey._initialized is True @pytest.mark.parametrize( ("key_string", "key_type"), [ (dsa_public_key_pem, dsa.DSAPublicKey), (ec_public_key_pem, ec.EllipticCurvePublicKey), (ed25519_public_key_pem, ed25519.Ed25519PublicKey), (ed448_public_key_pem, ed448.Ed448PublicKey), (rsa_public_key_pem, rsa.RSAPublicKey), ], ) def test_convert_roundtrip_cryptography_public_key( self, key_string, key_type ): """ PKey.from_cryptography_key creates a proper public PKey. PKey.to_cryptography_key creates a proper cryptography public key. """ key = serialization.load_pem_public_key(key_string, None) assert isinstance(key, key_type) pkey = PKey.from_cryptography_key(key) assert isinstance(pkey, PKey) parsed_key = pkey.to_cryptography_key() assert isinstance(parsed_key, key_type) assert parsed_key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) == key.public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ) assert pkey._only_public is True assert pkey._initialized is True def test_convert_from_cryptography_public_key(self): """ PKey.from_cryptography_key creates a proper public PKey. """ key = serialization.load_pem_public_key(cleartextPublicKeyPEM) pkey = PKey.from_cryptography_key(key) assert isinstance(pkey, PKey) assert pkey.bits() == key.key_size assert pkey._only_public is True assert pkey._initialized is True def test_convert_from_cryptography_unsupported_type(self): """ PKey.from_cryptography_key raises TypeError with an unsupported type. """ key = serialization.load_pem_private_key(x25519_private_key_pem, None) with pytest.raises(TypeError): PKey.from_cryptography_key(key) def test_convert_public_pkey_to_cryptography_key(self): """ PKey.to_cryptography_key creates a proper cryptography public key. """ pkey = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM) key = pkey.to_cryptography_key() assert isinstance(key, rsa.RSAPublicKey) assert pkey.bits() == key.key_size def test_type(self): """ `PKey` can be used to create instances of that type. """ assert is_consistent_type(PKey, "PKey") def test_construction(self): """ `PKey` takes no arguments and returns a new `PKey` instance. """ key = PKey() assert isinstance(key, PKey) def test_pregeneration(self): """ `PKey.bits` and `PKey.type` return `0` before the key is generated. `PKey.check` raises `TypeError` before the key is generated. """ key = PKey() assert key.type() == 0 assert key.bits() == 0 with pytest.raises(TypeError): key.check() def test_failed_generation(self): """ `PKey.generate_key` takes two arguments, the first giving the key type as one of `TYPE_RSA` or `TYPE_DSA` and the second giving the number of bits to generate. If an invalid type is specified or generation fails, `Error` is raised. If an invalid number of bits is specified, `ValueError` or `Error` is raised. """ key = PKey() with pytest.raises(TypeError): key.generate_key("foo", "bar") with pytest.raises(Error): key.generate_key(-1, 0) with pytest.raises(ValueError): key.generate_key(TYPE_RSA, -1) with pytest.raises(ValueError): key.generate_key(TYPE_RSA, 0) with pytest.raises(TypeError): key.generate_key(TYPE_RSA, object()) # XXX RSA generation for small values of bits is fairly buggy in a wide # range of OpenSSL versions. I need to figure out what the safe lower # bound for a reasonable number of OpenSSL versions is and explicitly # check for that in the wrapper. The failure behavior is typically an # infinite loop inside OpenSSL. # with pytest.raises(Error): # key.generate_key(TYPE_RSA, 2) # XXX DSA generation seems happy with any number of bits. The DSS # says bits must be between 512 and 1024 inclusive. OpenSSL's DSA # generator doesn't seem to care about the upper limit at all. For # the lower limit, it uses 512 if anything smaller is specified. # So, it doesn't seem possible to make generate_key fail for # TYPE_DSA with a bits argument which is at least an int. # with pytest.raises(Error): # key.generate_key(TYPE_DSA, -7) def test_rsa_generation(self): """ `PKey.generate_key` generates an RSA key when passed `TYPE_RSA` as a type and a reasonable number of bits. """ bits = 2048 key = PKey() key.generate_key(TYPE_RSA, bits) assert key.type() == TYPE_RSA assert key.bits() == bits assert key.check() def test_dsa_generation(self): """ `PKey.generate_key` generates a DSA key when passed `TYPE_DSA` as a type and a reasonable number of bits. """ # 512 is a magic number. The DSS (Digital Signature Standard) # allows a minimum of 512 bits for DSA. DSA_generate_parameters # will silently promote any value below 512 to 512. bits = 512 key = PKey() key.generate_key(TYPE_DSA, bits) assert key.type() == TYPE_DSA assert key.bits() == bits with pytest.raises(TypeError): key.check() def test_regeneration(self): """ `PKey.generate_key` can be called multiple times on the same key to generate new keys. """ key = PKey() for type, bits in [(TYPE_RSA, 2048), (TYPE_DSA, 576)]: key.generate_key(type, bits) assert key.type() == type assert key.bits() == bits def test_inconsistent_key(self): """ Either `load_privatekey` or `PKey.check` returns `Error` if the key is not consistent. """ with pytest.raises(Error): key = load_privatekey(FILETYPE_PEM, inconsistentPrivateKeyPEM) key.check() def test_check_public_key(self): """ `PKey.check` raises `TypeError` if only the public part of the key is available. """ # A trick to get a public-only key key = PKey() key.generate_key(TYPE_RSA, 2048) cert = X509() cert.set_pubkey(key) pub = cert.get_pubkey() with pytest.raises(TypeError): pub.check() def test_check_pr_897(self): """ Either `load_privatekey` or `PKey.check` raises `OpenSSL.crypto.Error` if provided with broken key """ with pytest.raises(Error): pkey = load_privatekey(FILETYPE_PEM, rsa_p_not_prime_pem) pkey.check() def x509_name(**attrs): """ Return a new X509Name with the given attributes. """ # XXX There's no other way to get a new X509Name yet. name = X509().get_subject() attrs = list(attrs.items()) # Make the order stable - order matters! def key(attr): return attr[1] attrs.sort(key=key) for k, v in attrs: setattr(name, k, v) return name class TestX509Name: """ Unit tests for `OpenSSL.crypto.X509Name`. """ def test_type(self): """ The type of X509Name objects is `X509Name`. """ name = x509_name() assert isinstance(name, X509Name) def test_only_string_attributes(self): """ Attempting to set a non-`str` attribute name on an `X509Name` instance causes `TypeError` to be raised. """ name = x509_name() # Beyond these cases, you may also think that unicode should be # rejected. Sorry, you're wrong. unicode is automatically converted # to str outside of the control of X509Name, so there's no way to # reject it. # Also, this used to test str subclasses, but that test is less # relevant now that the implementation is in Python instead of C. Also # PyPy automatically converts str subclasses to str when they are # passed to setattr, so we can't test it on PyPy. Apparently CPython # does this sometimes as well. with pytest.raises(TypeError): setattr(name, None, "hello") with pytest.raises(TypeError): setattr(name, 30, "hello") def test_set_invalid_attribute(self): """ Attempting to set any attribute name on an `X509Name` instance for which no corresponding NID is defined causes `AttributeError` to be raised. """ name = x509_name() with pytest.raises(AttributeError): setattr(name, "no such thing", None) def test_attributes(self): """ `X509Name` instances have attributes for each standard (?) X509Name field. """ name = x509_name() name.commonName = "foo" assert name.commonName == "foo" assert name.CN == "foo" name.CN = "baz" assert name.commonName == "baz" assert name.CN == "baz" name.commonName = "bar" assert name.commonName == "bar" assert name.CN == "bar" name.CN = "quux" assert name.commonName == "quux" assert name.CN == "quux" assert name.OU is None with pytest.raises(AttributeError): name.foobar def test_copy(self): """ `X509Name` creates a new `X509Name` instance with all the same attributes as an existing `X509Name` instance when called with one. """ name = x509_name(commonName="foo", emailAddress="bar@example.com") copy = X509Name(name) assert copy.commonName == "foo" assert copy.emailAddress == "bar@example.com" # Mutate the copy and ensure the original is unmodified. copy.commonName = "baz" assert name.commonName == "foo" # Mutate the original and ensure the copy is unmodified. name.emailAddress = "quux@example.com" assert copy.emailAddress == "bar@example.com" def test_repr(self): """ `repr` passed an `X509Name` instance should return a string containing a description of the type and the NIDs which have been set on it. """ name = x509_name(commonName="foo", emailAddress="bar") assert repr(name) == "" def test_comparison(self): """ `X509Name` instances should compare based on their NIDs. """ def _equality(a, b, assert_true, assert_false): assert_true(a == b) assert_false(a != b) assert_true(b == a) assert_false(b != a) def assert_true(x): assert x def assert_false(x): assert not x def assert_equal(a, b): _equality(a, b, assert_true, assert_false) # Instances compare equal to themselves. name = x509_name() assert_equal(name, name) # Empty instances should compare equal to each other. assert_equal(x509_name(), x509_name()) # Instances with equal NIDs should compare equal to each other. assert_equal(x509_name(commonName="foo"), x509_name(commonName="foo")) # Instance with equal NIDs set using different aliases should compare # equal to each other. assert_equal(x509_name(commonName="foo"), x509_name(CN="foo")) # Instances with more than one NID with the same values should compare # equal to each other. assert_equal( x509_name(CN="foo", organizationalUnitName="bar"), x509_name(commonName="foo", OU="bar"), ) def assert_not_equal(a, b): _equality(a, b, assert_false, assert_true) # Instances with different values for the same NID should not compare # equal to each other. assert_not_equal(x509_name(CN="foo"), x509_name(CN="bar")) # Instances with different NIDs should not compare equal to each other. assert_not_equal(x509_name(CN="foo"), x509_name(OU="foo")) assert_not_equal(x509_name(), object()) def _inequality(a, b, assert_true, assert_false): assert_true(a < b) assert_true(a <= b) assert_true(b > a) assert_true(b >= a) assert_false(a > b) assert_false(a >= b) assert_false(b < a) assert_false(b <= a) def assert_less_than(a, b): _inequality(a, b, assert_true, assert_false) # An X509Name with a NID with a value which sorts less than the value # of the same NID on another X509Name compares less than the other # X509Name. assert_less_than(x509_name(CN="abc"), x509_name(CN="def")) def assert_greater_than(a, b): _inequality(a, b, assert_false, assert_true) # An X509Name with a NID with a value which sorts greater than the # value of the same NID on another X509Name compares greater than the # other X509Name. assert_greater_than(x509_name(CN="def"), x509_name(CN="abc")) def assert_raises(a, b): with pytest.raises(TypeError): a < b with pytest.raises(TypeError): a <= b with pytest.raises(TypeError): a > b with pytest.raises(TypeError): a >= b # Only X509Name objects can be compared with lesser than / greater than assert_raises(x509_name(), object()) def test_hash(self): """ `X509Name.hash` returns an integer hash based on the value of the name. """ a = x509_name(CN="foo") b = x509_name(CN="foo") assert a.hash() == b.hash() a.CN = "bar" assert a.hash() != b.hash() def test_der(self): """ `X509Name.der` returns the DER encoded form of the name. """ a = x509_name(CN="foo", C="US") assert ( a.der() == b"0\x1b1\x0b0\t\x06\x03U\x04\x06\x13\x02US" b"1\x0c0\n\x06\x03U\x04\x03\x0c\x03foo" ) def test_get_components(self): """ `X509Name.get_components` returns a `list` of two-tuples of `str` giving the NIDs and associated values which make up the name. """ a = x509_name() assert a.get_components() == [] a.CN = "foo" assert a.get_components() == [(b"CN", b"foo")] a.organizationalUnitName = "bar" assert a.get_components() == [(b"CN", b"foo"), (b"OU", b"bar")] def test_load_nul_byte_attribute(self): """ An `X509Name` from an `X509` instance loaded from a file can have a NUL byte in the value of one of its attributes. """ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) subject = cert.get_subject() assert "null.python.org\x00example.org" == subject.commonName def test_load_nul_byte_components(self): """ An `X509Name` from an `X509` instance loaded from a file can have a NUL byte in the value of its components """ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) subject = cert.get_subject() components = subject.get_components() ccn = [value for name, value in components if name == b"CN"] assert ccn[0] == b"null.python.org\x00example.org" def test_set_attribute_failure(self): """ If the value of an attribute cannot be set for some reason then `Error` is raised. """ name = x509_name() # This value is too long with pytest.raises(Error): setattr(name, "O", b"x" * 512) class _PKeyInteractionTestsMixin: """ Tests which involve another thing and a PKey. """ def signable(self): """ Return something with `set_pubkey` and `sign` methods. """ raise NotImplementedError() def test_sign_with_ungenerated(self): """ `X509Req.sign` raises `ValueError` when passed a `PKey` with no parts. """ request = self.signable() key = PKey() with pytest.raises(ValueError): request.sign(key, GOOD_DIGEST) def test_sign_with_public_key(self): """ `X509Req.sign` raises `ValueError` when passed a `PKey` with no private part as the signing key. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 2048) request.set_pubkey(key) pub = request.get_pubkey() with pytest.raises(ValueError): request.sign(pub, GOOD_DIGEST) def test_sign_with_unknown_digest(self): """ `X509Req.sign` raises `ValueError` when passed a digest name which is not known. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(ValueError): request.sign(key, BAD_DIGEST) def test_sign(self): """ `X509Req.sign` succeeds when passed a private key object and a valid digest function. `X509Req.verify` can be used to check the signature. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 2048) request.set_pubkey(key) request.sign(key, GOOD_DIGEST) # If the type has a verify method, cover that too. if getattr(request, "verify", None) is not None: pub = request.get_pubkey() assert request.verify(pub) # Make another key that won't verify. key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(Error): request.verify(key) class TestX509Req(_PKeyInteractionTestsMixin): """ Tests for `OpenSSL.crypto.X509Req`. """ def signable(self): """ Create and return a new `X509Req`. """ return X509Req() def test_type(self): """ `X509Req` can be used to create instances of that type. """ assert is_consistent_type(X509Req, "X509Req") def test_construction(self): """ `X509Req` takes no arguments and returns an `X509Req` instance. """ request = X509Req() assert isinstance(request, X509Req) def test_version(self): """ `X509Req.set_version` sets the X.509 version of the certificate request. `X509Req.get_version` returns the X.509 version of the certificate request. The only defined version is 0. """ request = X509Req() assert request.get_version() == 0 request.set_version(0) assert request.get_version() == 0 def test_version_wrong_args(self): """ `X509Req.set_version` raises `TypeError` if called with a non-`int` argument. """ request = X509Req() with pytest.raises(TypeError): request.set_version("foo") with pytest.raises(ValueError): request.set_version(2) def test_get_subject(self): """ `X509Req.get_subject` returns an `X509Name` for the subject of the request and which is valid even after the request object is otherwise dead. """ request = X509Req() subject = request.get_subject() assert isinstance(subject, X509Name) subject.commonName = "foo" assert request.get_subject().commonName == "foo" del request subject.commonName = "bar" assert subject.commonName == "bar" def test_add_extensions(self): """ `X509Req.add_extensions` accepts a `list` of `X509Extension` instances and adds them to the X509 request. """ request = X509Req() request.add_extensions( [X509Extension(b"basicConstraints", True, b"CA:false")] ) exts = request.get_extensions() assert len(exts) == 1 assert exts[0].get_short_name() == b"basicConstraints" assert exts[0].get_critical() == 1 assert exts[0].get_data() == b"0\x00" def test_get_extensions(self): """ `X509Req.get_extensions` returns a `list` of extensions added to this X509 request. """ request = X509Req() exts = request.get_extensions() assert exts == [] request.add_extensions( [ X509Extension(b"basicConstraints", True, b"CA:true"), X509Extension(b"keyUsage", False, b"digitalSignature"), ] ) exts = request.get_extensions() assert len(exts) == 2 assert exts[0].get_short_name() == b"basicConstraints" assert exts[0].get_critical() == 1 assert exts[0].get_data() == b"0\x03\x01\x01\xff" assert exts[1].get_short_name() == b"keyUsage" assert exts[1].get_critical() == 0 assert exts[1].get_data() == b"\x03\x02\x07\x80" # Requesting it a second time should return the same list exts = request.get_extensions() assert len(exts) == 2 def test_undef_oid(self): assert ( X509Extension( b"1.2.3.4.5.6.7", False, b"DER:05:00" ).get_short_name() == b"UNDEF" ) def test_add_extensions_wrong_args(self): """ `X509Req.add_extensions` raises `TypeError` if called with a non-`list`. Or it raises `ValueError` if called with a `list` containing objects other than `X509Extension` instances. """ request = X509Req() with pytest.raises(TypeError): request.add_extensions(object()) with pytest.raises(ValueError): request.add_extensions([object()]) def test_verify_wrong_args(self): """ `X509Req.verify` raises `TypeError` if passed anything other than a `PKey` instance as its single argument. """ request = X509Req() with pytest.raises(TypeError): request.verify(object()) def test_verify_uninitialized_key(self): """ `X509Req.verify` raises `OpenSSL.crypto.Error` if called with a `OpenSSL.crypto.PKey` which contains no key data. """ request = X509Req() pkey = PKey() with pytest.raises(Error): request.verify(pkey) def test_verify_wrong_key(self): """ `X509Req.verify` raises `OpenSSL.crypto.Error` if called with a `OpenSSL.crypto.PKey` which does not represent the public part of the key which signed the request. """ request = X509Req() pkey = load_privatekey(FILETYPE_PEM, root_key_pem) request.set_pubkey(pkey) request.sign(pkey, GOOD_DIGEST) another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) with pytest.raises(Error): request.verify(another_pkey) def test_verify_success(self): """ `X509Req.verify` returns `True` if called with a `OpenSSL.crypto.PKey` which represents the public part of the key which signed the request. """ request = X509Req() pkey = load_privatekey(FILETYPE_PEM, root_key_pem) request.set_pubkey(pkey) request.sign(pkey, GOOD_DIGEST) assert request.verify(pkey) def test_convert_from_cryptography(self): crypto_req = x509.load_pem_x509_csr(cleartextCertificateRequestPEM) req = X509Req.from_cryptography(crypto_req) assert isinstance(req, X509Req) def test_convert_from_cryptography_unsupported_type(self): with pytest.raises(TypeError): X509Req.from_cryptography(object()) def test_convert_to_cryptography_key(self): req = load_certificate_request( FILETYPE_PEM, cleartextCertificateRequestPEM ) crypto_req = req.to_cryptography() assert isinstance(crypto_req, x509.CertificateSigningRequest) class TestX509(_PKeyInteractionTestsMixin): """ Tests for `OpenSSL.crypto.X509`. """ pemData = root_cert_pem + root_key_pem def signable(self): """ Create and return a new `X509`. """ certificate = X509() # Fill in placeholder validity values. signable only expects to call # set_pubkey and sign. certificate.gmtime_adj_notBefore(-24 * 60 * 60) certificate.gmtime_adj_notAfter(24 * 60 * 60) return certificate def test_type(self): """ `X509` can be used to create instances of that type. """ assert is_consistent_type(X509, "X509") def test_construction(self): """ `X509` takes no arguments and returns an instance of `X509`. """ certificate = X509() assert isinstance(certificate, X509) assert type(certificate).__name__ == "X509" assert type(certificate) is X509 def test_set_version_wrong_args(self): """ `X509.set_version` raises `TypeError` if invoked with an argument not of type `int`. """ cert = X509() with pytest.raises(TypeError): cert.set_version(None) def test_version(self): """ `X509.set_version` sets the certificate version number. `X509.get_version` retrieves it. """ cert = X509() cert.set_version(2) assert cert.get_version() == 2 def test_serial_number(self): """ The serial number of an `X509` can be retrieved and modified with `X509.get_serial_number` and `X509.set_serial_number`. """ certificate = X509() with pytest.raises(TypeError): certificate.set_serial_number("1") assert certificate.get_serial_number() == 0 certificate.set_serial_number(1) assert certificate.get_serial_number() == 1 certificate.set_serial_number(2**32 + 1) assert certificate.get_serial_number() == 2**32 + 1 certificate.set_serial_number(2**64 + 1) assert certificate.get_serial_number() == 2**64 + 1 certificate.set_serial_number(2**128 + 1) assert certificate.get_serial_number() == 2**128 + 1 def _setBoundTest(self, which): """ `X509.set_notBefore` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the beginning of the certificate's validity period to it. """ certificate = X509() set = getattr(certificate, "set_not" + which) get = getattr(certificate, "get_not" + which) # Starts with no value. assert get() is None # GMT (Or is it UTC?) -exarkun when = b"20040203040506Z" set(when) assert get() == when # A plus two hours and thirty minutes offset when = b"20040203040506+0530" set(when) assert get() == when # A minus one hour fifteen minutes offset when = b"20040203040506-0115" set(when) assert get() == when # An invalid string results in a ValueError with pytest.raises(ValueError): set(b"foo bar") # The wrong number of arguments results in a TypeError. with pytest.raises(TypeError): set() with pytest.raises(TypeError): set(b"20040203040506Z", b"20040203040506Z") with pytest.raises(TypeError): get(b"foo bar") # XXX ASN1_TIME (not GENERALIZEDTIME) def test_set_notBefore(self): """ `X509.set_notBefore` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the beginning of the certificate's validity period to it. """ self._setBoundTest("Before") def test_set_notAfter(self): """ `X509.set_notAfter` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the end of the certificate's validity period to it. """ self._setBoundTest("After") def test_get_notBefore(self): """ `X509.get_notBefore` returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ cert = load_certificate(FILETYPE_PEM, old_root_cert_pem) assert cert.get_notBefore() == b"20090325123658Z" def test_get_notAfter(self): """ `X509.get_notAfter` returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ cert = load_certificate(FILETYPE_PEM, old_root_cert_pem) assert cert.get_notAfter() == b"20170611123658Z" def test_gmtime_adj_notBefore_wrong_args(self): """ `X509.gmtime_adj_notBefore` raises `TypeError` if called with a non-`int` argument. """ cert = X509() with pytest.raises(TypeError): cert.gmtime_adj_notBefore(None) @pytest.mark.flaky(reruns=2) def test_gmtime_adj_notBefore(self): """ `X509.gmtime_adj_notBefore` changes the not-before timestamp to be the current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) not_before_min = utcnow().replace(microsecond=0) + timedelta( seconds=100 ) cert.gmtime_adj_notBefore(100) not_before = datetime.strptime( cert.get_notBefore().decode(), "%Y%m%d%H%M%SZ" ) not_before_max = utcnow() + timedelta(seconds=100) assert not_before_min <= not_before <= not_before_max def test_gmtime_adj_notAfter_wrong_args(self): """ `X509.gmtime_adj_notAfter` raises `TypeError` if called with a non-`int` argument. """ cert = X509() with pytest.raises(TypeError): cert.gmtime_adj_notAfter(None) @pytest.mark.flaky(reruns=2) def test_gmtime_adj_notAfter(self): """ `X509.gmtime_adj_notAfter` changes the not-after timestamp to be the current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) not_after_min = utcnow().replace(microsecond=0) + timedelta( seconds=100 ) cert.gmtime_adj_notAfter(100) not_after = datetime.strptime( cert.get_notAfter().decode(), "%Y%m%d%H%M%SZ" ) not_after_max = utcnow() + timedelta(seconds=100) assert not_after_min <= not_after <= not_after_max def test_has_expired(self): """ `X509.has_expired` returns `True` if the certificate's not-after time is in the past. """ cert = X509() cert.gmtime_adj_notAfter(-1) assert cert.has_expired() def test_has_not_expired(self): """ `X509.has_expired` returns `False` if the certificate's not-after time is in the future. """ cert = X509() cert.gmtime_adj_notAfter(2) assert not cert.has_expired() def test_has_expired_exception(self): """ `X509.has_expired` throws ValueError if not-after time is not set """ cert = X509() with pytest.raises(ValueError): cert.has_expired() def test_root_has_not_expired(self): """ `X509.has_expired` returns `False` if the certificate's not-after time is in the future. """ cert = load_certificate(FILETYPE_PEM, root_cert_pem) assert not cert.has_expired() def test_digest(self): """ `X509.digest` returns a string giving ":"-separated hex-encoded words of the digest of the certificate. """ cert = load_certificate(FILETYPE_PEM, old_root_cert_pem) assert ( # Digest verified with the command: # openssl x509 -in root_cert.pem -noout -fingerprint -sha256 cert.digest("SHA256") == ( b"3E:0F:16:39:6B:B1:3E:4F:08:85:C6:5F:10:0D:CB:2C:" b"25:C2:91:4E:D0:4A:C2:29:06:BD:55:E3:A7:B3:B7:06" ) ) def _extcert(self, pkey, extensions): cert = X509() # Certificates with extensions must be X.509v3, which is encoded with a # version of two. cert.set_version(2) cert.set_pubkey(pkey) cert.get_subject().commonName = "Unit Tests" cert.get_issuer().commonName = "Unit Tests" when = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") cert.set_notBefore(when) cert.set_notAfter(when) cert.add_extensions(extensions) cert.sign(pkey, "sha256") return load_certificate( FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert) ) def test_extension_count(self): """ `X509.get_extension_count` returns the number of extensions that are present in the certificate. """ pkey = load_privatekey(FILETYPE_PEM, client_key_pem) ca = X509Extension(b"basicConstraints", True, b"CA:FALSE") key = X509Extension(b"keyUsage", True, b"digitalSignature") subjectAltName = X509Extension( b"subjectAltName", True, b"DNS:example.com" ) # Try a certificate with no extensions at all. c = self._extcert(pkey, []) assert c.get_extension_count() == 0 # And a certificate with one c = self._extcert(pkey, [ca]) assert c.get_extension_count() == 1 # And a certificate with several c = self._extcert(pkey, [ca, key, subjectAltName]) assert c.get_extension_count() == 3 def test_get_extension(self): """ `X509.get_extension` takes an integer and returns an `X509Extension` corresponding to the extension at that index. """ pkey = load_privatekey(FILETYPE_PEM, client_key_pem) ca = X509Extension(b"basicConstraints", True, b"CA:FALSE") key = X509Extension(b"keyUsage", True, b"digitalSignature") subjectAltName = X509Extension( b"subjectAltName", False, b"DNS:example.com" ) cert = self._extcert(pkey, [ca, key, subjectAltName]) ext = cert.get_extension(0) assert isinstance(ext, X509Extension) assert ext.get_critical() assert ext.get_short_name() == b"basicConstraints" ext = cert.get_extension(1) assert isinstance(ext, X509Extension) assert ext.get_critical() assert ext.get_short_name() == b"keyUsage" ext = cert.get_extension(2) assert isinstance(ext, X509Extension) assert not ext.get_critical() assert ext.get_short_name() == b"subjectAltName" with pytest.raises(IndexError): cert.get_extension(-1) with pytest.raises(IndexError): cert.get_extension(4) with pytest.raises(TypeError): cert.get_extension("hello") def test_nullbyte_subjectAltName(self): """ The fields of a `subjectAltName` extension on an X509 may contain NUL bytes and this value is reflected in the string representation of the extension object. """ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) ext = cert.get_extension(3) assert ext.get_short_name() == b"subjectAltName" assert ( b"DNS:altnull.python.org\x00example.com, " b"email:null@python.org\x00user@example.org, " b"URI:http://null.python.org\x00http://example.org, " b"IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1" == str(ext).encode("ascii").strip() ) def test_invalid_digest_algorithm(self): """ `X509.digest` raises `ValueError` if called with an unrecognized hash algorithm. """ cert = X509() with pytest.raises(ValueError): cert.digest(BAD_DIGEST) def test_get_subject(self): """ `X509.get_subject` returns an `X509Name` instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_subject() assert isinstance(subj, X509Name) assert subj.get_components() == [ (b"C", b"US"), (b"ST", b"IL"), (b"L", b"Chicago"), (b"O", b"Testing"), (b"CN", b"Testing Root CA"), ] def test_set_subject_wrong_args(self): """ `X509.set_subject` raises a `TypeError` if called with an argument not of type `X509Name`. """ cert = X509() with pytest.raises(TypeError): cert.set_subject(None) def test_set_subject(self): """ `X509.set_subject` changes the subject of the certificate to the one passed in. """ cert = X509() name = cert.get_subject() name.C = "AU" name.OU = "Unit Tests" cert.set_subject(name) assert cert.get_subject().get_components() == [ (b"C", b"AU"), (b"OU", b"Unit Tests"), ] def test_get_issuer(self): """ `X509.get_issuer` returns an `X509Name` instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_issuer() assert isinstance(subj, X509Name) comp = subj.get_components() assert comp == [ (b"C", b"US"), (b"ST", b"IL"), (b"L", b"Chicago"), (b"O", b"Testing"), (b"CN", b"Testing Root CA"), ] def test_set_issuer_wrong_args(self): """ `X509.set_issuer` raises a `TypeError` if called with an argument not of type `X509Name`. """ cert = X509() with pytest.raises(TypeError): cert.set_issuer(None) def test_set_issuer(self): """ `X509.set_issuer` changes the issuer of the certificate to the one passed in. """ cert = X509() name = cert.get_issuer() name.C = "AU" name.OU = "Unit Tests" cert.set_issuer(name) assert cert.get_issuer().get_components() == [ (b"C", b"AU"), (b"OU", b"Unit Tests"), ] def test_get_pubkey_uninitialized(self): """ When called on a certificate with no public key, `X509.get_pubkey` raises `OpenSSL.crypto.Error`. """ cert = X509() with pytest.raises(Error): cert.get_pubkey() def test_set_pubkey_wrong_type(self): """ `X509.set_pubkey` raises `TypeError` when given an object of the wrong type. """ cert = X509() with pytest.raises(TypeError): cert.set_pubkey(object()) def test_subject_name_hash(self): """ `X509.subject_name_hash` returns the hash of the certificate's subject name. """ cert = load_certificate(FILETYPE_PEM, self.pemData) # SHA1 assert cert.subject_name_hash() == 3278919224 def test_get_signature_algorithm(self): """ `X509.get_signature_algorithm` returns a string which means the algorithm used to sign the certificate. """ cert = load_certificate(FILETYPE_PEM, self.pemData) assert b"sha256WithRSAEncryption" == cert.get_signature_algorithm() def test_get_undefined_signature_algorithm(self): """ `X509.get_signature_algorithm` raises `ValueError` if the signature algorithm is undefined or unknown. """ # This certificate has been modified to indicate a bogus OID in the # signature algorithm field so that OpenSSL does not recognize it. certPEM = b"""\ -----BEGIN CERTIFICATE----- MIIC/zCCAmigAwIBAgIBATAGBgJ8BQUAMHsxCzAJBgNVBAYTAlNHMREwDwYDVQQK EwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5 cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0 MS5jb20wHhcNMDAwOTEwMDk1MTMwWhcNMDIwOTEwMDk1MTMwWjBTMQswCQYDVQQG EwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsG CSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBI AkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5AdNgLzJ1/MfsQQJ7hHVeHmTAjM664V +fXvwUGJLziCeBo1ysWLRnl8CQIDAQABo4IBBDCCAQAwCQYDVR0TBAIwADAsBglg hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O BBYEFM+EgpK+eyZiwFU1aOPSbczbPSpVMIGlBgNVHSMEgZ0wgZqAFPuHI2nrnDqT FeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlw dG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtNMkNyeXB0byBDZXJ0 aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tggEA MA0GCSqGSIb3DQEBBAUAA4GBADv8KpPo+gfJxN2ERK1Y1l17sz/ZhzoGgm5XCdbx jEY7xKfpQngV599k1xhl11IMqizDwu0855agrckg2MCTmOI9DZzDD77tAYb+Dk0O PEVk0Mk/V0aIsDE9bolfCi/i/QWZ3N8s5nTWMNyBBBmoSliWCm4jkkRZRD0ejgTN tgI5 -----END CERTIFICATE----- """ cert = load_certificate(FILETYPE_PEM, certPEM) with pytest.raises(ValueError): cert.get_signature_algorithm() def test_sign_bad_pubkey_type(self): """ `X509.sign` raises `TypeError` when called with the wrong type. """ cert = X509() with pytest.raises(TypeError): cert.sign(object(), b"sha256") def test_convert_from_cryptography(self): crypto_cert = x509.load_pem_x509_certificate(intermediate_cert_pem) cert = X509.from_cryptography(crypto_cert) assert isinstance(cert, X509) assert cert.get_version() == crypto_cert.version.value def test_convert_from_cryptography_unsupported_type(self): with pytest.raises(TypeError): X509.from_cryptography(object()) def test_convert_to_cryptography_key(self): cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) crypto_cert = cert.to_cryptography() assert isinstance(crypto_cert, x509.Certificate) assert crypto_cert.version.value == cert.get_version() class TestX509Store: """ Test for `OpenSSL.crypto.X509Store`. """ def test_type(self): """ `X509Store` is a type object. """ assert is_consistent_type(X509Store, "X509Store") def test_add_cert(self): """ `X509Store.add_cert` adds a `X509` instance to the certificate store. """ cert = load_certificate(FILETYPE_PEM, root_cert_pem) store = X509Store() store.add_cert(cert) @pytest.mark.parametrize("cert", [None, 1.0, "cert", object()]) def test_add_cert_wrong_args(self, cert): """ `X509Store.add_cert` raises `TypeError` if passed a non-X509 object as its first argument. """ store = X509Store() with pytest.raises(TypeError): store.add_cert(cert) def test_add_cert_accepts_duplicate(self): """ `X509Store.add_cert` doesn't raise `OpenSSL.crypto.Error` if an attempt is made to add the same certificate to the store more than once. """ cert = load_certificate(FILETYPE_PEM, root_cert_pem) store = X509Store() store.add_cert(cert) store.add_cert(cert) @pytest.mark.parametrize( "cafile, capath, call_cafile, call_capath", [ ( "/cafile" + NON_ASCII, None, b"/cafile" + NON_ASCII.encode(sys.getfilesystemencoding()), _ffi.NULL, ), ( b"/cafile" + NON_ASCII.encode("utf-8"), None, b"/cafile" + NON_ASCII.encode("utf-8"), _ffi.NULL, ), ( None, "/capath" + NON_ASCII, _ffi.NULL, b"/capath" + NON_ASCII.encode(sys.getfilesystemencoding()), ), ( None, b"/capath" + NON_ASCII.encode("utf-8"), _ffi.NULL, b"/capath" + NON_ASCII.encode("utf-8"), ), ], ) def test_load_locations_parameters( self, cafile, capath, call_cafile, call_capath, monkeypatch ): class LibMock: def load_locations(self, store, cafile, capath): self.cafile = cafile self.capath = capath return 1 lib_mock = LibMock() monkeypatch.setattr( _lib, "X509_STORE_load_locations", lib_mock.load_locations ) store = X509Store() store.load_locations(cafile=cafile, capath=capath) assert call_cafile == lib_mock.cafile assert call_capath == lib_mock.capath def test_load_locations_fails_when_all_args_are_none(self): store = X509Store() with pytest.raises(Error): store.load_locations(None, None) def test_load_locations_raises_error_on_failure(self, tmpdir): invalid_ca_file = tmpdir.join("invalid.pem") invalid_ca_file.write("This is not a certificate") store = X509Store() with pytest.raises(Error): store.load_locations(cafile=str(invalid_ca_file)) def _runopenssl(pem, *args): """ Run the command line openssl tool with the given arguments and write the given PEM to its stdin. Not safe for quotes. """ proc = Popen([b"openssl", *list(args)], stdin=PIPE, stdout=PIPE) proc.stdin.write(pem) proc.stdin.close() output = proc.stdout.read() proc.stdout.close() proc.wait() return output class TestLoadPublicKey: """ Tests for :func:`load_publickey`. """ def test_loading_works(self): """ load_publickey loads public keys and sets correct attributes. """ key = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM) assert True is key._only_public assert 2048 == key.bits() assert TYPE_RSA == key.type() def test_invalid_type(self): """ load_publickey doesn't support FILETYPE_TEXT. """ with pytest.raises(ValueError): load_publickey(FILETYPE_TEXT, cleartextPublicKeyPEM) def test_invalid_key_format(self): """ load_publickey explodes on incorrect keys. """ with pytest.raises(Error): load_publickey(FILETYPE_ASN1, cleartextPublicKeyPEM) def test_tolerates_unicode_strings(self): """ load_publickey works with text strings, not just bytes. """ serialized = cleartextPublicKeyPEM.decode("ascii") key = load_publickey(FILETYPE_PEM, serialized) dumped_pem = dump_publickey(FILETYPE_PEM, key) assert dumped_pem == cleartextPublicKeyPEM class TestFunction: """ Tests for free-functions in the `OpenSSL.crypto` module. """ def test_load_privatekey_invalid_format(self): """ `load_privatekey` raises `ValueError` if passed an unknown filetype. """ with pytest.raises(ValueError): load_privatekey(100, root_key_pem) def test_load_privatekey_invalid_passphrase_type(self): """ `load_privatekey` raises `TypeError` if passed a passphrase that is neither a `str` nor a callable. """ with pytest.raises(TypeError): load_privatekey( FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object() ) def test_load_privatekey_wrongPassphrase(self): """ `load_privatekey` raises `OpenSSL.crypto.Error` when it is passed an encrypted PEM and an incorrect passphrase. """ with pytest.raises(Error) as err: load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, b"quack") assert err.value.args[0] != [] def test_load_privatekey_passphraseWrongType(self): """ `load_privatekey` raises `ValueError` when it is passeda passphrase with a private key encoded in a format, that doesn't support encryption. """ key = load_privatekey(FILETYPE_PEM, root_key_pem) blob = dump_privatekey(FILETYPE_ASN1, key) with pytest.raises(ValueError): load_privatekey(FILETYPE_ASN1, blob, "secret") def test_load_privatekey_passphrase(self): """ `load_privatekey` can create a `PKey` object from an encrypted PEM string if given the passphrase. """ key = load_privatekey( FILETYPE_PEM, encryptedPrivateKeyPEM, encryptedPrivateKeyPEMPassphrase, ) assert isinstance(key, PKey) def test_load_privatekey_passphrase_exception(self): """ If the passphrase callback raises an exception, that exception is raised by `load_privatekey`. """ def cb(ignored): raise ArithmeticError with pytest.raises(ArithmeticError): load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) def test_load_privatekey_wrongPassphraseCallback(self): """ `load_privatekey` raises `OpenSSL.crypto.Error` when it is passed an encrypted PEM and a passphrase callback which returns an incorrect passphrase. """ called = [] def cb(*a): called.append(None) return b"quack" with pytest.raises(Error) as err: load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) assert called assert err.value.args[0] != [] def test_load_privatekey_passphraseCallback(self): """ `load_privatekey` can create a `PKey` object from an encrypted PEM string if given a passphrase callback which returns the correct password. """ called = [] def cb(writing): called.append(writing) return encryptedPrivateKeyPEMPassphrase key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) assert isinstance(key, PKey) assert called == [False] def test_load_privatekey_passphrase_wrong_return_type(self): """ `load_privatekey` raises `ValueError` if the passphrase callback returns something other than a byte string. """ with pytest.raises(ValueError): load_privatekey( FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3 ) def test_dump_privatekey_wrong_args(self): """ `dump_privatekey` raises `TypeError` if called with a `cipher` argument but no `passphrase` argument. """ key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(TypeError): dump_privatekey(FILETYPE_PEM, key, cipher=GOOD_CIPHER) def test_dump_privatekey_not_rsa_key(self): """ `dump_privatekey` raises `TypeError` if called with a key that is not RSA. """ key = PKey() key.generate_key(TYPE_DSA, 512) with pytest.raises(TypeError): dump_privatekey(FILETYPE_TEXT, key) def test_dump_privatekey_invalid_pkey(self): with pytest.raises(TypeError): dump_privatekey(FILETYPE_TEXT, object()) def test_dump_privatekey_unknown_cipher(self): """ `dump_privatekey` raises `ValueError` if called with an unrecognized cipher name. """ key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(ValueError): dump_privatekey(FILETYPE_PEM, key, BAD_CIPHER, "passphrase") def test_dump_privatekey_invalid_passphrase_type(self): """ `dump_privatekey` raises `TypeError` if called with a passphrase which is neither a `str` nor a callable. """ key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(TypeError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, object()) def test_dump_privatekey_invalid_filetype(self): """ `dump_privatekey` raises `ValueError` if called with an unrecognized filetype. """ key = PKey() key.generate_key(TYPE_RSA, 2048) with pytest.raises(ValueError): dump_privatekey(100, key) def test_load_privatekey_passphrase_callback_length(self): """ `crypto.load_privatekey` should raise an error when the passphrase provided by the callback is too long, not silently truncate it. """ def cb(ignored): return "a" * 1025 with pytest.raises(ValueError): load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) def test_dump_privatekey_passphrase(self): """ `dump_privatekey` writes an encrypted PEM when given a passphrase. """ passphrase = b"foo" key = load_privatekey(FILETYPE_PEM, root_key_pem) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase) assert isinstance(pem, bytes) loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) assert isinstance(loadedKey, PKey) assert loadedKey.type() == key.type() assert loadedKey.bits() == key.bits() def test_dump_privatekey_passphrase_wrong_type(self): """ `dump_privatekey` raises `ValueError` when it is passed a passphrase with a private key encoded in a format, that doesn't support encryption. """ key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ValueError): dump_privatekey(FILETYPE_ASN1, key, GOOD_CIPHER, "secret") def test_dump_certificate(self): """ `dump_certificate` writes PEM, DER, and text. """ pemData = root_cert_pem + root_key_pem cert = load_certificate(FILETYPE_PEM, pemData) dumped_pem = dump_certificate(FILETYPE_PEM, cert) assert dumped_pem == root_cert_pem dumped_der = dump_certificate(FILETYPE_ASN1, cert) good_der = _runopenssl(dumped_pem, b"x509", b"-outform", b"DER") assert dumped_der == good_der cert2 = load_certificate(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_certificate(FILETYPE_PEM, cert2) assert dumped_pem2 == root_cert_pem dumped_text = dump_certificate(FILETYPE_TEXT, cert) assert len(dumped_text) > 500 def test_dump_certificate_bad_type(self): """ `dump_certificate` raises a `ValueError` if it's called with a bad type. """ cert = load_certificate(FILETYPE_PEM, root_cert_pem) with pytest.raises(ValueError): dump_certificate(object(), cert) def test_dump_privatekey_pem(self): """ `dump_privatekey` writes a PEM """ key = load_privatekey(FILETYPE_PEM, root_key_pem) assert key.check() dumped_pem = dump_privatekey(FILETYPE_PEM, key) assert dumped_pem == normalized_root_key_pem def test_dump_privatekey_asn1(self): """ `dump_privatekey` writes a DER """ key = load_privatekey(FILETYPE_PEM, root_key_pem) dumped_der = dump_privatekey(FILETYPE_ASN1, key) assert dumped_der == root_key_der def test_load_privatekey_asn1(self): """ `dump_privatekey` writes a DER """ key = load_privatekey(FILETYPE_ASN1, root_key_der) assert key.bits() == 3072 assert key.type() == TYPE_RSA def test_dump_privatekey_text(self): """ `dump_privatekey` writes a text """ key = load_privatekey(FILETYPE_PEM, root_key_pem) dumped_text = dump_privatekey(FILETYPE_TEXT, key) assert len(dumped_text) > 500 def test_dump_publickey_pem(self): """ dump_publickey writes a PEM. """ key = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM) dumped_pem = dump_publickey(FILETYPE_PEM, key) assert dumped_pem == cleartextPublicKeyPEM def test_dump_publickey_asn1(self): """ dump_publickey writes a DER. """ key = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM) dumped_der = dump_publickey(FILETYPE_ASN1, key) key2 = load_publickey(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_publickey(FILETYPE_PEM, key2) assert dumped_pem2 == cleartextPublicKeyPEM def test_dump_publickey_invalid_type(self): """ dump_publickey doesn't support FILETYPE_TEXT. """ key = load_publickey(FILETYPE_PEM, cleartextPublicKeyPEM) with pytest.raises(ValueError): dump_publickey(FILETYPE_TEXT, key) def test_dump_certificate_request(self): """ `dump_certificate_request` writes a PEM, DER, and text. """ req = load_certificate_request( FILETYPE_PEM, cleartextCertificateRequestPEM ) dumped_pem = dump_certificate_request(FILETYPE_PEM, req) assert dumped_pem == cleartextCertificateRequestPEM dumped_der = dump_certificate_request(FILETYPE_ASN1, req) good_der = _runopenssl(dumped_pem, b"req", b"-outform", b"DER") assert dumped_der == good_der req2 = load_certificate_request(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2) assert dumped_pem2 == cleartextCertificateRequestPEM dumped_text = dump_certificate_request(FILETYPE_TEXT, req) assert len(dumped_text) > 500 with pytest.raises(ValueError): dump_certificate_request(100, req) def test_dump_privatekey_passphrase_callback(self): """ `dump_privatekey` writes an encrypted PEM when given a callback which returns the correct passphrase. """ passphrase = b"foo" called = [] def cb(writing): called.append(writing) return passphrase key = load_privatekey(FILETYPE_PEM, root_key_pem) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) assert isinstance(pem, bytes) assert called == [True] loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) assert isinstance(loadedKey, PKey) assert loadedKey.type() == key.type() assert loadedKey.bits() == key.bits() def test_dump_privatekey_passphrase_exception(self): """ `dump_privatekey` should not overwrite the exception raised by the passphrase callback. """ def cb(ignored): raise ArithmeticError key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ArithmeticError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) def test_dump_privatekey_passphraseCallbackLength(self): """ `crypto.dump_privatekey` should raise an error when the passphrase provided by the callback is too long, not silently truncate it. """ def cb(ignored): return "a" * 1025 key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ValueError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) def test_dump_privatekey_truncated(self): """ `crypto.dump_privatekey` should not truncate a passphrase that contains a null byte. """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) passphrase = b"foo\x00bar" truncated_passphrase = passphrase.split(b"\x00", 1)[0] # By dumping with the full passphrase load should raise an error if we # try to load using the truncated passphrase. If dump truncated the # passphrase, then we WILL load the privatekey and the test fails encrypted_key_pem = dump_privatekey( FILETYPE_PEM, key, "AES-256-CBC", passphrase ) with pytest.raises(Error): load_privatekey( FILETYPE_PEM, encrypted_key_pem, truncated_passphrase ) def test_load_privatekey_truncated(self): """ `crypto.load_privatekey` should not truncate a passphrase that contains a null byte. """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) passphrase = b"foo\x00bar" truncated_passphrase = passphrase.split(b"\x00", 1)[0] # By dumping using the truncated passphrase load should raise an error # if we try to load using the full passphrase. If load truncated the # passphrase, then we WILL load the privatekey and the test fails encrypted_key_pem = dump_privatekey( FILETYPE_PEM, key, "AES-256-CBC", truncated_passphrase ) with pytest.raises(Error): load_privatekey(FILETYPE_PEM, encrypted_key_pem, passphrase) class TestLoadCertificate: """ Tests for `load_certificate_request`. """ def test_bad_file_type(self): """ If the file type passed to `load_certificate_request` is neither `FILETYPE_PEM` nor `FILETYPE_ASN1` then `ValueError` is raised. """ with pytest.raises(ValueError): load_certificate_request(object(), b"") with pytest.raises(ValueError): load_certificate(object(), b"") def test_bad_certificate(self): """ If the bytes passed to `load_certificate` are not a valid certificate, an exception is raised. """ with pytest.raises(Error): load_certificate(FILETYPE_ASN1, b"lol") class TestRevoked: """ Tests for `OpenSSL.crypto.Revoked`. """ def test_ignores_unsupported_revoked_cert_extension_get_reason(self): """ The get_reason method on the Revoked class checks to see if the extension is NID_crl_reason and should skip it otherwise. This test loads a CRL with extensions it should ignore. """ crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) revoked = crl.get_revoked() reason = revoked[1].get_reason() assert reason == b"Unspecified" def test_ignores_unsupported_revoked_cert_extension_set_new_reason(self): crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) revoked = crl.get_revoked() revoked[1].set_reason(None) reason = revoked[1].get_reason() assert reason is None def test_construction(self): """ Confirm we can create `OpenSSL.crypto.Revoked`. Check that it is empty. """ revoked = Revoked() assert isinstance(revoked, Revoked) assert type(revoked) is Revoked assert revoked.get_serial() == b"00" assert revoked.get_rev_date() is None assert revoked.get_reason() is None def test_serial(self): """ Confirm we can set and get serial numbers from `OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. """ revoked = Revoked() ret = revoked.set_serial(b"10b") assert ret is None ser = revoked.get_serial() assert ser == b"010B" revoked.set_serial(b"31ppp") # a type error would be nice ser = revoked.get_serial() assert ser == b"31" with pytest.raises(ValueError): revoked.set_serial(b"pqrst") with pytest.raises(TypeError): revoked.set_serial(100) def test_date(self): """ Confirm we can set and get revocation dates from `OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. """ revoked = Revoked() date = revoked.get_rev_date() assert date is None now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") ret = revoked.set_rev_date(now) assert ret is None date = revoked.get_rev_date() assert date == now def test_reason(self): """ Confirm we can set and get revocation reasons from `OpenSSL.crypto.Revoked`. The "get" need to work as "set". Likewise, each reason of all_reasons() must work. """ revoked = Revoked() for r in revoked.all_reasons(): for x in range(2): ret = revoked.set_reason(r) assert ret is None reason = revoked.get_reason() assert reason.lower().replace(b" ", b"") == r.lower().replace( b" ", b"" ) r = reason # again with the resp of get revoked.set_reason(None) assert revoked.get_reason() is None @pytest.mark.parametrize("reason", [object(), 1.0, "foo"]) def test_set_reason_wrong_args(self, reason): """ `Revoked.set_reason` raises `TypeError` if called with an argument which is neither `None` nor a byte string. """ revoked = Revoked() with pytest.raises(TypeError): revoked.set_reason(reason) def test_set_reason_invalid_reason(self): """ Calling `OpenSSL.crypto.Revoked.set_reason` with an argument which isn't a valid reason results in `ValueError` being raised. """ revoked = Revoked() with pytest.raises(ValueError): revoked.set_reason(b"blue") class TestCRL: """ Tests for `OpenSSL.crypto.CRL`. """ cert = load_certificate(FILETYPE_PEM, root_cert_pem) pkey = load_privatekey(FILETYPE_PEM, root_key_pem) root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) root_key = load_privatekey(FILETYPE_PEM, root_key_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem ) intermediate_server_key = load_privatekey( FILETYPE_PEM, intermediate_server_key_pem ) def test_construction(self): """ Confirm we can create `OpenSSL.crypto.CRL`. Check that it is empty """ crl = CRL() assert isinstance(crl, CRL) assert crl.get_revoked() is None def _get_crl(self): """ Get a new ``CRL`` with a revocation. """ crl = CRL() revoked = Revoked() now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") revoked.set_rev_date(now) revoked.set_serial(b"3ab") revoked.set_reason(b"sUpErSeDEd") crl.add_revoked(revoked) return crl def test_export_pem(self): """ If not passed a format, ``CRL.export`` returns a "PEM" format string representing a serial number, a revoked reason, and certificate issuer information. """ # PEM format dumped_crl = self._get_crl().export( self.cert, self.pkey, days=20, digest=b"sha256" ) crl = x509.load_pem_x509_crl(dumped_crl) revoked = crl.get_revoked_certificate_by_serial_number(0x03AB) assert revoked is not None assert crl.issuer == x509.Name( [ x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "IL"), x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Chicago"), x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Testing"), x509.NameAttribute( x509.NameOID.COMMON_NAME, "Testing Root CA" ), ] ) def test_export_der(self): """ If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a "DER" format string representing a serial number, a revoked reason, and certificate issuer information. """ crl = self._get_crl() # DER format dumped_crl = self._get_crl().export( self.cert, self.pkey, FILETYPE_ASN1, digest=b"sha256" ) crl = x509.load_der_x509_crl(dumped_crl) revoked = crl.get_revoked_certificate_by_serial_number(0x03AB) assert revoked is not None assert crl.issuer == x509.Name( [ x509.NameAttribute(x509.NameOID.COUNTRY_NAME, "US"), x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, "IL"), x509.NameAttribute(x509.NameOID.LOCALITY_NAME, "Chicago"), x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, "Testing"), x509.NameAttribute( x509.NameOID.COMMON_NAME, "Testing Root CA" ), ] ) def test_export_text(self): """ If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a text format string like the one produced by the openssl command line tool. """ crl = self._get_crl() # text format dumped_text = crl.export( self.cert, self.pkey, type=FILETYPE_TEXT, digest=b"sha256" ) assert len(dumped_text) > 500 def test_export_custom_digest(self): """ If passed the name of a digest function, ``CRL.export`` uses a signature algorithm based on that digest function. """ crl = self._get_crl() dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha384") text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b"Signature Algorithm: sha384") def test_export_md5_digest(self): """ If passed md5 as the digest function, ``CRL.export`` uses md5 and does not emit a deprecation warning. """ crl = self._get_crl() with warnings.catch_warnings(record=True) as catcher: warnings.simplefilter("always") assert 0 == len(catcher) dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5") text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b"Signature Algorithm: md5") def test_export_default_digest(self): """ If not passed the name of a digest function, ``CRL.export`` raises a ``TypeError``. """ crl = self._get_crl() with pytest.raises(TypeError): crl.export(self.cert, self.pkey) def test_export_invalid(self): """ If `CRL.export` is used with an uninitialized `X509` instance, `OpenSSL.crypto.Error` is raised. """ crl = CRL() with pytest.raises(Error): crl.export(X509(), PKey(), digest=b"sha256") def test_add_revoked_keyword(self): """ `OpenSSL.CRL.add_revoked` accepts its single argument as the ``revoked`` keyword argument. """ crl = CRL() revoked = Revoked() revoked.set_serial(b"01") revoked.set_rev_date(b"20160310020145Z") crl.add_revoked(revoked=revoked) assert isinstance(crl.get_revoked()[0], Revoked) def test_export_wrong_args(self): """ Calling `OpenSSL.CRL.export` with arguments other than the certificate, private key, integer file type, and integer number of days it expects, results in a `TypeError` being raised. """ crl = CRL() with pytest.raises(TypeError): crl.export(None, self.pkey, FILETYPE_PEM, 10) with pytest.raises(TypeError): crl.export(self.cert, None, FILETYPE_PEM, 10) with pytest.raises(TypeError): crl.export(self.cert, self.pkey, None, 10) with pytest.raises(TypeError): crl.export(self.cert, FILETYPE_PEM, None) def test_export_unknown_filetype(self): """ Calling `OpenSSL.CRL.export` with a file type other than `FILETYPE_PEM`, `FILETYPE_ASN1`, or `FILETYPE_TEXT` results in a `ValueError` being raised. """ crl = CRL() with pytest.raises(ValueError): crl.export(self.cert, self.pkey, 100, 10, digest=b"sha256") def test_export_unknown_digest(self): """ Calling `OpenSSL.CRL.export` with an unsupported digest results in a `ValueError` being raised. """ crl = CRL() with pytest.raises(ValueError): crl.export( self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" ) def test_get_revoked(self): """ Use python to create a simple CRL with two revocations. Get back the `Revoked` using `OpenSSL.CRL.get_revoked` and verify them. """ crl = CRL() revoked = Revoked() now = datetime.now().strftime("%Y%m%d%H%M%SZ").encode("ascii") revoked.set_rev_date(now) revoked.set_serial(b"3ab") crl.add_revoked(revoked) revoked.set_serial(b"100") revoked.set_reason(b"sUpErSeDEd") crl.add_revoked(revoked) revs = crl.get_revoked() assert len(revs) == 2 assert type(revs[0]) is Revoked assert type(revs[1]) is Revoked assert revs[0].get_serial() == b"03AB" assert revs[1].get_serial() == b"0100" assert revs[0].get_rev_date() == now assert revs[1].get_rev_date() == now def test_load_crl(self): """ Load a known CRL and inspect its revocations. Both EM and DER formats are loaded. """ crl = load_crl(FILETYPE_PEM, crlData) revs = crl.get_revoked() assert len(revs) == 2 assert revs[0].get_serial() == b"03AB" assert revs[0].get_reason() is None assert revs[1].get_serial() == b"0100" assert revs[1].get_reason() == b"Superseded" der = _runopenssl(crlData, b"crl", b"-outform", b"DER") crl = load_crl(FILETYPE_ASN1, der) revs = crl.get_revoked() assert len(revs) == 2 assert revs[0].get_serial() == b"03AB" assert revs[0].get_reason() is None assert revs[1].get_serial() == b"0100" assert revs[1].get_reason() == b"Superseded" def test_load_crl_bad_filetype(self): """ Calling `OpenSSL.crypto.load_crl` with an unknown file type raises a `ValueError`. """ with pytest.raises(ValueError): load_crl(100, crlData) def test_load_crl_bad_data(self): """ Calling `OpenSSL.crypto.load_crl` with file data which can't be loaded raises a `OpenSSL.crypto.Error`. """ with pytest.raises(Error): load_crl(FILETYPE_PEM, b"hello, world") def test_get_issuer(self): """ Load a known CRL and assert its issuer's common name is what we expect from the encoded crlData string. """ crl = load_crl(FILETYPE_PEM, crlData) assert isinstance(crl.get_issuer(), X509Name) assert crl.get_issuer().CN == "Testing Root CA" def test_dump_crl(self): """ The dumped CRL matches the original input. """ crl = load_crl(FILETYPE_PEM, crlData) buf = dump_crl(FILETYPE_PEM, crl) assert buf == crlData @staticmethod def _make_test_crl(issuer_cert, issuer_key, certs=()): """ Create a CRL. :param list[X509] certs: A list of certificates to revoke. :rtype: CRL """ crl = CRL() for cert in certs: revoked = Revoked() # FIXME: This string splicing is an unfortunate implementation # detail that has been reported in # https://github.com/pyca/pyopenssl/issues/258 serial = hex(cert.get_serial_number())[2:].encode("utf-8") revoked.set_serial(serial) revoked.set_reason(b"unspecified") revoked.set_rev_date(b"20140601000000Z") crl.add_revoked(revoked) crl.set_version(1) crl.set_lastUpdate(b"20140601000000Z") # The year 5000 is far into the future so that this CRL isn't # considered to have expired. crl.set_nextUpdate(b"50000601000000Z") crl.sign(issuer_cert, issuer_key, digest=b"sha512") return crl @staticmethod def _make_test_crl_cryptography(issuer_cert, issuer_key, certs=()): """ Create a CRL using cryptography's API. :param list[X509] certs: A list of certificates to revoke. :rtype: ``cryptography.x509.CertificateRevocationList`` """ from cryptography.x509.extensions import CRLReason, ReasonFlags builder = x509.CertificateRevocationListBuilder() builder = builder.issuer_name( X509.to_cryptography(issuer_cert).subject ) for cert in certs: revoked = ( x509.RevokedCertificateBuilder() .serial_number(cert.get_serial_number()) .revocation_date(datetime(2014, 6, 1, 0, 0, 0)) .add_extension(CRLReason(ReasonFlags.unspecified), False) .build() ) builder = builder.add_revoked_certificate(revoked) builder = builder.last_update(datetime(2014, 6, 1, 0, 0, 0)) # The year 5000 is far into the future so that this CRL isn't # considered to have expired. builder = builder.next_update(datetime(5000, 6, 1, 0, 0, 0)) crl = builder.sign( private_key=PKey.to_cryptography_key(issuer_key), algorithm=hashes.SHA512(), ) return crl @pytest.mark.parametrize( "create_crl", [ pytest.param( _make_test_crl.__func__, id="pyOpenSSL CRL", ), pytest.param( _make_test_crl_cryptography.__func__, id="cryptography CRL", ), ], ) def test_verify_with_revoked(self, create_crl): """ `verify_certificate` raises error when an intermediate certificate is revoked. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) root_crl = create_crl( self.root_cert, self.root_key, certs=[self.intermediate_cert] ) intermediate_crl = create_crl( self.intermediate_cert, self.intermediate_key, certs=[] ) store.add_crl(root_crl) store.add_crl(intermediate_crl) store.set_flags( X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL ) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as err: store_ctx.verify_certificate() assert str(err.value) == "certificate revoked" @pytest.mark.parametrize( "create_crl", [ pytest.param( _make_test_crl.__func__, id="pyOpenSSL CRL", ), pytest.param( _make_test_crl_cryptography.__func__, id="cryptography CRL", ), ], ) def test_verify_with_missing_crl(self, create_crl): """ `verify_certificate` raises error when an intermediate certificate's CRL is missing. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) root_crl = create_crl( self.root_cert, self.root_key, certs=[self.intermediate_cert] ) store.add_crl(root_crl) store.set_flags( X509StoreFlags.CRL_CHECK | X509StoreFlags.CRL_CHECK_ALL ) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as err: store_ctx.verify_certificate() assert str(err.value) == "unable to get certificate CRL" assert err.value.certificate.get_subject().CN == "intermediate-service" def test_convert_from_cryptography(self): crypto_crl = x509.load_pem_x509_crl(crlData) crl = CRL.from_cryptography(crypto_crl) assert isinstance(crl, CRL) def test_convert_from_cryptography_unsupported_type(self): with pytest.raises(TypeError): CRL.from_cryptography(object()) def test_convert_to_cryptography_key(self): crl = load_crl(FILETYPE_PEM, crlData) crypto_crl = crl.to_cryptography() assert isinstance(crypto_crl, x509.CertificateRevocationList) class TestX509StoreContext: """ Tests for `OpenSSL.crypto.X509StoreContext`. """ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) intermediate_server_cert = load_certificate( FILETYPE_PEM, intermediate_server_cert_pem ) def test_valid(self): """ `verify_certificate` returns ``None`` when called with a certificate and valid chain. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) assert store_ctx.verify_certificate() is None def test_reuse(self): """ `verify_certificate` can be called multiple times with the same ``X509StoreContext`` instance to produce the same result. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) assert store_ctx.verify_certificate() is None assert store_ctx.verify_certificate() is None @pytest.mark.parametrize( "root_cert, chain, verified_cert", [ pytest.param( root_cert, [intermediate_cert], intermediate_server_cert, id="intermediate in chain", ), pytest.param( root_cert, [], intermediate_cert, id="empty chain", ), pytest.param( root_cert, [root_cert, intermediate_server_cert, intermediate_cert], intermediate_server_cert, id="extra certs in chain", ), ], ) def test_verify_success_with_chain(self, root_cert, chain, verified_cert): store = X509Store() store.add_cert(root_cert) store_ctx = X509StoreContext(store, verified_cert, chain=chain) assert store_ctx.verify_certificate() is None def test_valid_untrusted_chain_reuse(self): """ `verify_certificate` using an untrusted chain can be called multiple times with the same ``X509StoreContext`` instance to produce the same result. """ store = X509Store() store.add_cert(self.root_cert) chain = [self.intermediate_cert] store_ctx = X509StoreContext( store, self.intermediate_server_cert, chain=chain ) assert store_ctx.verify_certificate() is None assert store_ctx.verify_certificate() is None def test_chain_reference(self): """ ``X509StoreContext`` properly keeps references to the untrusted chain certificates. """ store = X509Store() store.add_cert(self.root_cert) chain = [load_certificate(FILETYPE_PEM, intermediate_cert_pem)] store_ctx = X509StoreContext( store, self.intermediate_server_cert, chain=chain ) del chain assert store_ctx.verify_certificate() is None @pytest.mark.parametrize( "root_cert, chain, verified_cert", [ pytest.param( root_cert, [], intermediate_server_cert, id="intermediate missing", ), pytest.param( None, [intermediate_cert], intermediate_server_cert, id="no trusted root", ), pytest.param( None, [root_cert, intermediate_cert], intermediate_server_cert, id="untrusted root, full chain is available", ), pytest.param( intermediate_cert, [root_cert, intermediate_cert], intermediate_server_cert, id="untrusted root, intermediate is trusted and in chain", ), ], ) def test_verify_fail_with_chain(self, root_cert, chain, verified_cert): store = X509Store() if root_cert: store.add_cert(root_cert) store_ctx = X509StoreContext(store, verified_cert, chain=chain) with pytest.raises(X509StoreContextError): store_ctx.verify_certificate() @pytest.mark.parametrize( "chain, expected_error", [ pytest.param( [intermediate_cert, "This is not a certificate"], TypeError, id="non-certificate in chain", ), pytest.param( 42, TypeError, id="non-list chain", ), ], ) def test_untrusted_chain_wrong_args(self, chain, expected_error): """ Creating ``X509StoreContext`` with wrong chain raises an exception. """ store = X509Store() store.add_cert(self.root_cert) with pytest.raises(expected_error): X509StoreContext(store, self.intermediate_server_cert, chain=chain) def test_failure_building_untrusted_chain_raises(self, monkeypatch): """ Creating ``X509StoreContext`` raises ``OpenSSL.crypto.Error`` when the underlying lib fails to add the certificate to the stack. """ monkeypatch.setattr(_lib, "sk_X509_push", lambda _stack, _x509: -1) store = X509Store() store.add_cert(self.root_cert) chain = [self.intermediate_cert] with pytest.raises(Error): X509StoreContext(store, self.intermediate_server_cert, chain=chain) def test_trusted_self_signed(self): """ `verify_certificate` returns ``None`` when called with a self-signed certificate and itself in the chain. """ store = X509Store() store.add_cert(self.root_cert) store_ctx = X509StoreContext(store, self.root_cert) assert store_ctx.verify_certificate() is None def test_untrusted_self_signed(self): """ `verify_certificate` raises error when a self-signed certificate is verified without itself in the chain. """ store = X509Store() store_ctx = X509StoreContext(store, self.root_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() # OpenSSL 1.1.x and 3.0.x have different error messages assert str(exc.value) in [ "self signed certificate", "self-signed certificate", ] assert exc.value.certificate.get_subject().CN == "Testing Root CA" def test_invalid_chain_no_root(self): """ `verify_certificate` raises error when a root certificate is missing from the chain. """ store = X509Store() store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() assert str(exc.value) == "unable to get issuer certificate" assert exc.value.certificate.get_subject().CN == "intermediate" def test_invalid_chain_no_intermediate(self): """ `verify_certificate` raises error when an intermediate certificate is missing from the chain. """ store = X509Store() store.add_cert(self.root_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() assert str(exc.value) == "unable to get local issuer certificate" assert exc.value.certificate.get_subject().CN == "intermediate-service" def test_modification_pre_verify(self): """ `verify_certificate` can use a store context modified after instantiation. """ store_bad = X509Store() store_bad.add_cert(self.intermediate_cert) store_good = X509Store() store_good.add_cert(self.root_cert) store_good.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store_bad, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() assert str(exc.value) == "unable to get issuer certificate" assert exc.value.certificate.get_subject().CN == "intermediate" store_ctx.set_store(store_good) assert store_ctx.verify_certificate() is None def test_verify_with_time(self): """ `verify_certificate` raises error when the verification time is set at notAfter. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) expire_time = self.intermediate_server_cert.get_notAfter() expire_datetime = datetime.strptime( expire_time.decode("utf-8"), "%Y%m%d%H%M%SZ" ) store.set_time(expire_datetime) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() assert str(exc.value) == "certificate has expired" def test_get_verified_chain(self): """ `get_verified_chain` returns the verified chain. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) chain = store_ctx.get_verified_chain() assert len(chain) == 3 intermediate_subject = self.intermediate_server_cert.get_subject() assert chain[0].get_subject() == intermediate_subject assert chain[1].get_subject() == self.intermediate_cert.get_subject() assert chain[2].get_subject() == self.root_cert.get_subject() # Test reuse chain = store_ctx.get_verified_chain() assert len(chain) == 3 assert chain[0].get_subject() == intermediate_subject assert chain[1].get_subject() == self.intermediate_cert.get_subject() assert chain[2].get_subject() == self.root_cert.get_subject() def test_get_verified_chain_invalid_chain_no_root(self): """ `get_verified_chain` raises error when cert verification fails. """ store = X509Store() store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.get_verified_chain() assert str(exc.value) == "unable to get issuer certificate" assert exc.value.certificate.get_subject().CN == "intermediate" @pytest.fixture def root_ca_file(self, tmpdir): return self._create_ca_file(tmpdir, "root_ca_hash_dir", self.root_cert) @pytest.fixture def intermediate_ca_file(self, tmpdir): return self._create_ca_file( tmpdir, "intermediate_ca_hash_dir", self.intermediate_cert ) @staticmethod def _create_ca_file(base_path, hash_directory, cacert): ca_hash = f"{cacert.subject_name_hash():08x}.0" cafile = base_path.join(hash_directory, ca_hash) cafile.write_binary( dump_certificate(FILETYPE_PEM, cacert), ensure=True ) return cafile def test_verify_with_ca_file_location(self, root_ca_file): store = X509Store() store.load_locations(str(root_ca_file)) store_ctx = X509StoreContext(store, self.intermediate_cert) store_ctx.verify_certificate() def test_verify_with_ca_path_location(self, root_ca_file): store = X509Store() store.load_locations(None, str(root_ca_file.dirname)) store_ctx = X509StoreContext(store, self.intermediate_cert) store_ctx.verify_certificate() def test_verify_with_cafile_and_capath( self, root_ca_file, intermediate_ca_file ): store = X509Store() store.load_locations( cafile=str(root_ca_file), capath=str(intermediate_ca_file.dirname) ) store_ctx = X509StoreContext(store, self.intermediate_server_cert) store_ctx.verify_certificate() def test_verify_with_multiple_ca_files( self, root_ca_file, intermediate_ca_file ): store = X509Store() store.load_locations(str(root_ca_file)) store.load_locations(str(intermediate_ca_file)) store_ctx = X509StoreContext(store, self.intermediate_server_cert) store_ctx.verify_certificate() def test_verify_failure_with_empty_ca_directory(self, tmpdir): store = X509Store() store.load_locations(None, str(tmpdir)) store_ctx = X509StoreContext(store, self.intermediate_cert) with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() assert str(exc.value) == "unable to get local issuer certificate" def test_verify_with_partial_chain(self): store = X509Store() store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) with pytest.raises(X509StoreContextError): store_ctx.verify_certificate() # Now set the partial verification flag for verification. store.set_flags(X509StoreFlags.PARTIAL_CHAIN) store_ctx = X509StoreContext(store, self.intermediate_server_cert) assert store_ctx.verify_certificate() is None class TestSignVerify: """ Tests for `OpenSSL.crypto.sign` and `OpenSSL.crypto.verify`. """ def test_sign_verify(self): """ `sign` generates a cryptographic signature which `verify` can check. """ content = ( b"It was a bright cold day in April, and the clocks were striking " b"thirteen. Winston Smith, his chin nuzzled into his breast in an " b"effort to escape the vile wind, slipped quickly through the " b"glass doors of Victory Mansions, though not quickly enough to " b"prevent a swirl of gritty dust from entering along with him." ) # sign the content with this private key priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) # verify the content with this cert good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) # certificate unrelated to priv_key, used to trigger an error bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) for digest in ["md5", "sha1", "sha256"]: sig = sign(priv_key, content, digest) # Verify the signature of content, will throw an exception if # error. verify(good_cert, sig, content, digest) # This should fail because the certificate doesn't match the # private key that was used to sign the content. with pytest.raises(Error): verify(bad_cert, sig, content, digest) # This should fail because we've "tainted" the content after # signing it. with pytest.raises(Error): verify(good_cert, sig, content + b"tainted", digest) # test that unknown digest types fail with pytest.raises(ValueError): sign(priv_key, content, "strange-digest") with pytest.raises(ValueError): verify(good_cert, sig, content, "strange-digest") def test_sign_verify_with_text(self): """ `sign` generates a cryptographic signature which `verify` can check. Deprecation warnings raised because using text instead of bytes as content """ content = ( b"It was a bright cold day in April, and the clocks were striking " b"thirteen. Winston Smith, his chin nuzzled into his breast in an " b"effort to escape the vile wind, slipped quickly through the " b"glass doors of Victory Mansions, though not quickly enough to " b"prevent a swirl of gritty dust from entering along with him." ).decode("ascii") priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) cert = load_certificate(FILETYPE_PEM, root_cert_pem) for digest in ["md5", "sha1", "sha256"]: with pytest.warns(DeprecationWarning) as w: warnings.simplefilter("always") sig = sign(priv_key, content, digest) assert ( f"{WARNING_TYPE_EXPECTED} for data is no longer accepted, " f"use bytes" ) == str(w[-1].message) with pytest.warns(DeprecationWarning) as w: warnings.simplefilter("always") verify(cert, sig, content, digest) assert ( f"{WARNING_TYPE_EXPECTED} for data is no longer accepted, " f"use bytes" ) == str(w[-1].message) def test_sign_verify_ecdsa(self): """ `sign` generates a cryptographic signature which `verify` can check. ECDSA Signatures in the X9.62 format may have variable length, different from the length of the private key. """ content = ( b"It was a bright cold day in April, and the clocks were striking " b"thirteen. Winston Smith, his chin nuzzled into his breast in an " b"effort to escape the vile wind, slipped quickly through the " b"glass doors of Victory Mansions, though not quickly enough to " b"prevent a swirl of gritty dust from entering along with him." ) priv_key = load_privatekey(FILETYPE_PEM, ec_root_key_pem) cert = load_certificate(FILETYPE_PEM, ec_root_cert_pem) sig = sign(priv_key, content, "sha256") verify(cert, sig, content, "sha256") def test_sign_nulls(self): """ `sign` produces a signature for a string with embedded nulls. """ content = b"Watch out! \0 Did you see it?" priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) sig = sign(priv_key, content, "sha256") verify(good_cert, sig, content, "sha256") def test_sign_with_large_key(self): """ `sign` produces a signature for a string when using a long key. """ content = ( b"It was a bright cold day in April, and the clocks were striking " b"thirteen. Winston Smith, his chin nuzzled into his breast in an " b"effort to escape the vile wind, slipped quickly through the " b"glass doors of Victory Mansions, though not quickly enough to " b"prevent a swirl of gritty dust from entering along with him." ) priv_key = load_privatekey(FILETYPE_PEM, large_key_pem) sign(priv_key, content, "sha256") class TestEllipticCurve: """ Tests for `_EllipticCurve`, `get_elliptic_curve`, and `get_elliptic_curves`. """ def test_set(self): """ `get_elliptic_curves` returns a `set`. """ assert isinstance(get_elliptic_curves(), set) def test_a_curve(self): """ `get_elliptic_curve` can be used to retrieve a particular supported curve. """ curves = get_elliptic_curves() curve = next(iter(curves)) assert curve.name == get_elliptic_curve(curve.name).name def test_not_a_curve(self): """ `get_elliptic_curve` raises `ValueError` if called with a name which does not identify a supported curve. """ with pytest.raises(ValueError): get_elliptic_curve("this curve was just invented") def test_repr(self): """ The string representation of a curve object includes simply states the object is a curve and what its name is. """ curves = get_elliptic_curves() curve = next(iter(curves)) assert f"" == repr(curve) def test_to_EC_KEY(self): """ The curve object can export a version of itself as an EC_KEY* via the private `_EllipticCurve._to_EC_KEY`. """ curves = get_elliptic_curves() curve = next(iter(curves)) # It's not easy to assert anything about this object. However, see # leakcheck/crypto.py for a test that demonstrates it at least does # not leak memory. curve._to_EC_KEY() class EllipticCurveFactory: """ A helper to get the names of two curves. """ def __init__(self): curves = iter(get_elliptic_curves()) self.curve_name = next(curves).name self.another_curve_name = next(curves).name class TestEllipticCurveEquality(EqualityTestsMixin): """ Tests `_EllipticCurve`'s implementation of ``==`` and ``!=``. """ curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: skip = "There are no curves available there can be no curve objects." def anInstance(self): """ Get the curve object for an arbitrary curve supported by the system. """ return get_elliptic_curve(self.curve_factory.curve_name) def anotherInstance(self): """ Get the curve object for an arbitrary curve supported by the system - but not the one returned by C{anInstance}. """ return get_elliptic_curve(self.curve_factory.another_curve_name) class TestEllipticCurveHash: """ Tests for `_EllipticCurve`'s implementation of hashing (thus use as an item in a `dict` or `set`). """ curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: skip = "There are no curves available there can be no curve objects." def test_contains(self): """ The ``in`` operator reports that a `set` containing a curve does contain that curve. """ curve = get_elliptic_curve(self.curve_factory.curve_name) curves = set([curve]) assert curve in curves def test_does_not_contain(self): """ The ``in`` operator reports that a `set` not containing a curve does not contain that curve. """ curve = get_elliptic_curve(self.curve_factory.curve_name) curves = set( [get_elliptic_curve(self.curve_factory.another_curve_name)] ) assert curve not in curves pyopenssl-24.2.1/tests/test_debug.py000066400000000000000000000003411465571700000175000ustar00rootroot00000000000000from OpenSSL import version from OpenSSL.debug import _env_info def test_debug_info(): """ Debug info contains correct data. """ # Just check a sample we control. assert version.__version__ in _env_info pyopenssl-24.2.1/tests/test_rand.py000066400000000000000000000014221465571700000173370ustar00rootroot00000000000000# Copyright (c) Frederick Dean # See LICENSE for details. """ Unit tests for `OpenSSL.rand`. """ import pytest from OpenSSL import rand class TestRand: @pytest.mark.parametrize("args", [(b"foo", None), (None, 3)]) def test_add_wrong_args(self, args): """ `OpenSSL.rand.add` raises `TypeError` if called with arguments not of type `str` and `int`. """ with pytest.raises(TypeError): rand.add(*args) def test_add(self): """ `OpenSSL.rand.add` adds entropy to the PRNG. """ rand.add(b"hamburger", 3) def test_status(self): """ `OpenSSL.rand.status` returns `1` if the PRNG has sufficient entropy, `0` otherwise. """ assert rand.status() == 1 pyopenssl-24.2.1/tests/test_ssl.py000066400000000000000000004637531465571700000172370ustar00rootroot00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Unit tests for :mod:`OpenSSL.SSL`. """ import datetime import gc import select import sys import time import uuid from errno import ( EAFNOSUPPORT, ECONNREFUSED, EINPROGRESS, EPIPE, ESHUTDOWN, EWOULDBLOCK, ) from gc import collect, get_referrers from os import makedirs from os.path import join from socket import ( AF_INET, AF_INET6, MSG_PEEK, SHUT_RDWR, gaierror, socket, ) from sys import getfilesystemencoding, platform from typing import Union from weakref import ref import pytest from cryptography import x509 from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.x509.oid import NameOID from pretend import raiser from OpenSSL import SSL from OpenSSL._util import ffi as _ffi from OpenSSL._util import lib as _lib from OpenSSL.crypto import ( FILETYPE_PEM, TYPE_RSA, X509, PKey, X509Store, dump_certificate, dump_privatekey, get_elliptic_curves, load_certificate, load_privatekey, ) with pytest.warns(DeprecationWarning): from OpenSSL.crypto import X509Extension from OpenSSL.SSL import ( DTLS_METHOD, MODE_RELEASE_BUFFERS, NO_OVERLAPPING_PROTOCOLS, OP_COOKIE_EXCHANGE, OP_NO_COMPRESSION, OP_NO_QUERY_MTU, OP_NO_TICKET, OP_SINGLE_DH_USE, OPENSSL_VERSION_NUMBER, RECEIVED_SHUTDOWN, SENT_SHUTDOWN, SESS_CACHE_BOTH, SESS_CACHE_CLIENT, SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL, SESS_CACHE_NO_INTERNAL_LOOKUP, SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_OFF, SESS_CACHE_SERVER, SSL_CB_ACCEPT_EXIT, SSL_CB_ACCEPT_LOOP, SSL_CB_ALERT, SSL_CB_CONNECT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_EXIT, SSL_CB_HANDSHAKE_DONE, SSL_CB_HANDSHAKE_START, SSL_CB_LOOP, SSL_CB_READ, SSL_CB_READ_ALERT, SSL_CB_WRITE, SSL_CB_WRITE_ALERT, SSL_ST_ACCEPT, SSL_ST_CONNECT, SSL_ST_MASK, SSLEAY_BUILT_ON, SSLEAY_CFLAGS, SSLEAY_DIR, SSLEAY_PLATFORM, SSLEAY_VERSION, TLS1_1_VERSION, TLS1_2_VERSION, TLS1_3_VERSION, TLS_METHOD, VERIFY_CLIENT_ONCE, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_NONE, VERIFY_PEER, Connection, Context, Error, OP_NO_SSLv2, OP_NO_SSLv3, Session, SSLeay_version, SSLv23_METHOD, SysCallError, TLSv1_1_METHOD, TLSv1_2_METHOD, TLSv1_METHOD, WantReadError, WantWriteError, ZeroReturnError, _make_requires, ) try: from OpenSSL.SSL import ( SSL_ST_BEFORE, SSL_ST_INIT, SSL_ST_OK, SSL_ST_RENEGOTIATE, ) except ImportError: SSL_ST_INIT = SSL_ST_BEFORE = SSL_ST_OK = SSL_ST_RENEGOTIATE = None try: from OpenSSL.SSL import OP_NO_TLSv1_3 except ImportError: OP_NO_TLSv1_3 = None from .test_crypto import ( client_cert_pem, client_key_pem, root_cert_pem, root_key_pem, server_cert_pem, server_key_pem, ) from .util import NON_ASCII, WARNING_TYPE_EXPECTED, is_consistent_type # openssl dhparam 2048 -out dh-2048.pem dhparam = """\ -----BEGIN DH PARAMETERS----- MIIBCAKCAQEA2F5e976d/GjsaCdKv5RMWL/YV7fq1UUWpPAer5fDXflLMVUuYXxE 3m3ayZob9lbpgEU0jlPAsXHfQPGxpKmvhv+xV26V/DEoukED8JeZUY/z4pigoptl +8+TYdNNE/rFSZQFXIp+v2D91IEgmHBnZlKFSbKR+p8i0KjExXGjU6ji3S5jkOku ogikc7df1Ui0hWNJCmTjExq07aXghk97PsdFSxjdawuG3+vos5bnNoUwPLYlFc/z ITYG0KXySiCLi4UDlXTZTz7u/+OYczPEgqa/JPUddbM/kfvaRAnjY38cfQ7qXf8Y i5s5yYK7a/0eWxxRr2qraYaUj8RwDpH9CwIBAg== -----END DH PARAMETERS----- """ def socket_any_family(): try: return socket(AF_INET) except OSError as e: if e.errno == EAFNOSUPPORT: return socket(AF_INET6) raise def loopback_address(socket): if socket.family == AF_INET: return "127.0.0.1" else: assert socket.family == AF_INET6 return "::1" def join_bytes_or_unicode(prefix, suffix): """ Join two path components of either ``bytes`` or ``unicode``. The return type is the same as the type of ``prefix``. """ # If the types are the same, nothing special is necessary. if type(prefix) is type(suffix): return join(prefix, suffix) # Otherwise, coerce suffix to the type of prefix. if isinstance(prefix, str): return join(prefix, suffix.decode(getfilesystemencoding())) else: return join(prefix, suffix.encode(getfilesystemencoding())) def verify_cb(conn, cert, errnum, depth, ok): return ok def socket_pair(): """ Establish and return a pair of network sockets connected to each other. """ # Connect a pair of sockets port = socket_any_family() port.bind(("", 0)) port.listen(1) client = socket(port.family) client.setblocking(False) client.connect_ex((loopback_address(port), port.getsockname()[1])) client.setblocking(True) server = port.accept()[0] port.close() # Let's pass some unencrypted data to make sure our socket connection is # fine. Just one byte, so we don't have to worry about buffers getting # filled up or fragmentation. server.send(b"x") assert client.recv(1024) == b"x" client.send(b"y") assert server.recv(1024) == b"y" # Most of our callers want non-blocking sockets, make it easy for them. server.setblocking(False) client.setblocking(False) return (server, client) def handshake(client, server): conns = [client, server] while conns: for conn in conns: try: conn.do_handshake() except WantReadError: pass else: conns.remove(conn) def _create_certificate_chain(): """ Construct and return a chain of certificates. 1. A new self-signed certificate authority certificate (cacert) 2. A new intermediate certificate signed by cacert (icert) 3. A new server certificate signed by icert (scert) """ caext = X509Extension(b"basicConstraints", False, b"CA:true") not_after_date = datetime.date.today() + datetime.timedelta(days=365) not_after = not_after_date.strftime("%Y%m%d%H%M%SZ").encode("ascii") # Step 1 cakey = PKey() cakey.generate_key(TYPE_RSA, 2048) cacert = X509() cacert.set_version(2) cacert.get_subject().commonName = "Authority Certificate" cacert.set_issuer(cacert.get_subject()) cacert.set_pubkey(cakey) cacert.set_notBefore(b"20000101000000Z") cacert.set_notAfter(not_after) cacert.add_extensions([caext]) cacert.set_serial_number(0) cacert.sign(cakey, "sha256") # Step 2 ikey = PKey() ikey.generate_key(TYPE_RSA, 2048) icert = X509() icert.set_version(2) icert.get_subject().commonName = "Intermediate Certificate" icert.set_issuer(cacert.get_subject()) icert.set_pubkey(ikey) icert.set_notBefore(b"20000101000000Z") icert.set_notAfter(not_after) icert.add_extensions([caext]) icert.set_serial_number(0) icert.sign(cakey, "sha256") # Step 3 skey = PKey() skey.generate_key(TYPE_RSA, 2048) scert = X509() scert.set_version(2) scert.get_subject().commonName = "Server Certificate" scert.set_issuer(icert.get_subject()) scert.set_pubkey(skey) scert.set_notBefore(b"20000101000000Z") scert.set_notAfter(not_after) scert.add_extensions( [X509Extension(b"basicConstraints", True, b"CA:false")] ) scert.set_serial_number(0) scert.sign(ikey, "sha256") return [(cakey, cacert), (ikey, icert), (skey, scert)] def loopback_client_factory(socket, version=SSLv23_METHOD): client = Connection(Context(version), socket) client.set_connect_state() return client def loopback_server_factory(socket, version=SSLv23_METHOD): ctx = Context(version) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(ctx, socket) server.set_accept_state() return server def loopback(server_factory=None, client_factory=None): """ Create a connected socket pair and force two connected SSL sockets to talk to each other via memory BIOs. """ if server_factory is None: server_factory = loopback_server_factory if client_factory is None: client_factory = loopback_client_factory (server, client) = socket_pair() server = server_factory(server) client = client_factory(client) handshake(client, server) server.setblocking(True) client.setblocking(True) return server, client def interact_in_memory(client_conn, server_conn): """ Try to read application bytes from each of the two `Connection` objects. Copy bytes back and forth between their send/receive buffers for as long as there is anything to copy. When there is nothing more to copy, return `None`. If one of them actually manages to deliver some application bytes, return a two-tuple of the connection from which the bytes were read and the bytes themselves. """ wrote = True while wrote: # Loop until neither side has anything to say wrote = False # Copy stuff from each side's send buffer to the other side's # receive buffer. for read, write in [ (client_conn, server_conn), (server_conn, client_conn), ]: # Give the side a chance to generate some more bytes, or succeed. try: data = read.recv(2**16) except WantReadError: # It didn't succeed, so we'll hope it generated some output. pass else: # It did succeed, so we'll stop now and let the caller deal # with it. return (read, data) while True: # Keep copying as long as there's more stuff there. try: dirty = read.bio_read(4096) except WantReadError: # Okay, nothing more waiting to be sent. Stop # processing this send buffer. break else: # Keep track of the fact that someone generated some # output. wrote = True write.bio_write(dirty) def handshake_in_memory(client_conn, server_conn): """ Perform the TLS handshake between two `Connection` instances connected to each other via memory BIOs. """ client_conn.set_connect_state() server_conn.set_accept_state() for conn in [client_conn, server_conn]: try: conn.do_handshake() except WantReadError: pass interact_in_memory(client_conn, server_conn) class TestVersion: """ Tests for version information exposed by `OpenSSL.SSL.SSLeay_version` and `OpenSSL.SSL.OPENSSL_VERSION_NUMBER`. """ def test_OPENSSL_VERSION_NUMBER(self): """ `OPENSSL_VERSION_NUMBER` is an integer with status in the low byte and the patch, fix, minor, and major versions in the nibbles above that. """ assert isinstance(OPENSSL_VERSION_NUMBER, int) def test_SSLeay_version(self): """ `SSLeay_version` takes a version type indicator and returns one of a number of version strings based on that indicator. """ versions = {} for t in [ SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON, SSLEAY_PLATFORM, SSLEAY_DIR, ]: version = SSLeay_version(t) versions[version] = t assert isinstance(version, bytes) assert len(versions) == 5 @pytest.fixture def ca_file(tmpdir): """ Create a valid PEM file with CA certificates and return the path. """ key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = key.public_key() builder = x509.CertificateBuilder() builder = builder.subject_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "pyopenssl.org")]) ) builder = builder.issuer_name( x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "pyopenssl.org")]) ) one_day = datetime.timedelta(1, 0, 0) builder = builder.not_valid_before(datetime.datetime.today() - one_day) builder = builder.not_valid_after(datetime.datetime.today() + one_day) builder = builder.serial_number(int(uuid.uuid4())) builder = builder.public_key(public_key) builder = builder.add_extension( x509.BasicConstraints(ca=True, path_length=None), critical=True, ) certificate = builder.sign(private_key=key, algorithm=hashes.SHA256()) ca_file = tmpdir.join("test.pem") ca_file.write_binary( certificate.public_bytes( encoding=serialization.Encoding.PEM, ) ) return str(ca_file).encode("ascii") @pytest.fixture def context(): """ A simple "best TLS you can get" context. TLS 1.2+ in any reasonable OpenSSL """ return Context(SSLv23_METHOD) class TestContext: """ Unit tests for `OpenSSL.SSL.Context`. """ @pytest.mark.parametrize( "cipher_string", [b"hello world:AES128-SHA", "hello world:AES128-SHA"], ) def test_set_cipher_list(self, context, cipher_string): """ `Context.set_cipher_list` accepts both byte and unicode strings for naming the ciphers which connections created with the context object will be able to choose from. """ context.set_cipher_list(cipher_string) conn = Connection(context, None) assert "AES128-SHA" in conn.get_cipher_list() def test_set_cipher_list_wrong_type(self, context): """ `Context.set_cipher_list` raises `TypeError` when passed a non-string argument. """ with pytest.raises(TypeError): context.set_cipher_list(object()) @pytest.mark.flaky(reruns=2) def test_set_cipher_list_no_cipher_match(self, context): """ `Context.set_cipher_list` raises `OpenSSL.SSL.Error` with a `"no cipher match"` reason string regardless of the TLS version. """ with pytest.raises(Error) as excinfo: context.set_cipher_list(b"imaginary-cipher") assert excinfo.value.args[0][0] in [ # 1.1.x ( "SSL routines", "SSL_CTX_set_cipher_list", "no cipher match", ), # 3.0.x ( "SSL routines", "", "no cipher match", ), ] def test_load_client_ca(self, context, ca_file): """ `Context.load_client_ca` works as far as we can tell. """ context.load_client_ca(ca_file) def test_load_client_ca_invalid(self, context, tmpdir): """ `Context.load_client_ca` raises an Error if the ca file is invalid. """ ca_file = tmpdir.join("test.pem") ca_file.write("") with pytest.raises(Error) as e: context.load_client_ca(str(ca_file).encode("ascii")) assert "PEM routines" == e.value.args[0][0][0] def test_load_client_ca_unicode(self, context, ca_file): """ Passing the path as unicode raises a warning but works. """ pytest.deprecated_call(context.load_client_ca, ca_file.decode("ascii")) def test_set_session_id(self, context): """ `Context.set_session_id` works as far as we can tell. """ context.set_session_id(b"abc") def test_set_session_id_fail(self, context): """ `Context.set_session_id` errors are propagated. """ with pytest.raises(Error) as e: context.set_session_id(b"abc" * 1000) assert e.value.args[0][0] in [ # 1.1.x ( "SSL routines", "SSL_CTX_set_session_id_context", "ssl session id context too long", ), # 3.0.x ( "SSL routines", "", "ssl session id context too long", ), ] def test_set_session_id_unicode(self, context): """ `Context.set_session_id` raises a warning if a unicode string is passed. """ pytest.deprecated_call(context.set_session_id, "abc") def test_method(self): """ `Context` can be instantiated with one of `SSLv2_METHOD`, `SSLv3_METHOD`, `SSLv23_METHOD`, `TLSv1_METHOD`, `TLSv1_1_METHOD`, or `TLSv1_2_METHOD`. """ methods = [SSLv23_METHOD, TLSv1_METHOD] for meth in methods: Context(meth) maybe = [TLSv1_1_METHOD, TLSv1_2_METHOD] for meth in maybe: try: Context(meth) except (Error, ValueError): # Some versions of OpenSSL have SSLv2 / TLSv1.1 / TLSv1.2, some # don't. Difficult to say in advance. pass with pytest.raises(TypeError): Context("") with pytest.raises(ValueError): Context(13) def test_type(self): """ `Context` can be used to create instances of that type. """ assert is_consistent_type(Context, "Context", TLSv1_METHOD) def test_use_privatekey_file_missing(self, tmpfile): """ `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` when passed the name of a file which does not exist. """ ctx = Context(SSLv23_METHOD) with pytest.raises(Error): ctx.use_privatekey_file(tmpfile) def _use_privatekey_file_test(self, pemfile, filetype): """ Verify that calling ``Context.use_privatekey_file`` with the given arguments does not raise an exception. """ key = PKey() key.generate_key(TYPE_RSA, 1024) with open(pemfile, "w") as pem: pem.write(dump_privatekey(FILETYPE_PEM, key).decode("ascii")) ctx = Context(SSLv23_METHOD) ctx.use_privatekey_file(pemfile, filetype) @pytest.mark.parametrize("filetype", [object(), "", None, 1.0]) def test_wrong_privatekey_file_wrong_args(self, tmpfile, filetype): """ `Context.use_privatekey_file` raises `TypeError` when called with a `filetype` which is not a valid file encoding. """ ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.use_privatekey_file(tmpfile, filetype) def test_use_privatekey_file_bytes(self, tmpfile): """ A private key can be specified from a file by passing a ``bytes`` instance giving the file name to ``Context.use_privatekey_file``. """ self._use_privatekey_file_test( tmpfile + NON_ASCII.encode(getfilesystemencoding()), FILETYPE_PEM, ) def test_use_privatekey_file_unicode(self, tmpfile): """ A private key can be specified from a file by passing a ``unicode`` instance giving the file name to ``Context.use_privatekey_file``. """ self._use_privatekey_file_test( tmpfile.decode(getfilesystemencoding()) + NON_ASCII, FILETYPE_PEM, ) def test_use_certificate_file_wrong_args(self): """ `Context.use_certificate_file` raises `TypeError` if the first argument is not a byte string or the second argument is not an integer. """ ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.use_certificate_file(object(), FILETYPE_PEM) with pytest.raises(TypeError): ctx.use_certificate_file(b"somefile", object()) with pytest.raises(TypeError): ctx.use_certificate_file(object(), FILETYPE_PEM) def test_use_certificate_file_missing(self, tmpfile): """ `Context.use_certificate_file` raises `OpenSSL.SSL.Error` if passed the name of a file which does not exist. """ ctx = Context(SSLv23_METHOD) with pytest.raises(Error): ctx.use_certificate_file(tmpfile) def _use_certificate_file_test(self, certificate_file): """ Verify that calling ``Context.use_certificate_file`` with the given filename doesn't raise an exception. """ # TODO # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as # long as check_privatekey works right we're good... with open(certificate_file, "wb") as pem_file: pem_file.write(root_cert_pem) ctx = Context(SSLv23_METHOD) ctx.use_certificate_file(certificate_file) def test_use_certificate_file_bytes(self, tmpfile): """ `Context.use_certificate_file` sets the certificate (given as a `bytes` filename) which will be used to identify connections created using the context. """ filename = tmpfile + NON_ASCII.encode(getfilesystemencoding()) self._use_certificate_file_test(filename) def test_use_certificate_file_unicode(self, tmpfile): """ `Context.use_certificate_file` sets the certificate (given as a `bytes` filename) which will be used to identify connections created using the context. """ filename = tmpfile.decode(getfilesystemencoding()) + NON_ASCII self._use_certificate_file_test(filename) def test_check_privatekey_valid(self): """ `Context.check_privatekey` returns `None` if the `Context` instance has been configured to use a matched key and certificate pair. """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, client_cert_pem) context = Context(SSLv23_METHOD) context.use_privatekey(key) context.use_certificate(cert) assert None is context.check_privatekey() def test_check_privatekey_invalid(self): """ `Context.check_privatekey` raises `Error` if the `Context` instance has been configured to use a key and certificate pair which don't relate to each other. """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) context = Context(SSLv23_METHOD) context.use_privatekey(key) context.use_certificate(cert) with pytest.raises(Error): context.check_privatekey() def test_app_data(self): """ `Context.set_app_data` stores an object for later retrieval using `Context.get_app_data`. """ app_data = object() context = Context(SSLv23_METHOD) context.set_app_data(app_data) assert context.get_app_data() is app_data def test_set_options_wrong_args(self): """ `Context.set_options` raises `TypeError` if called with a non-`int` argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_options(None) def test_set_options(self): """ `Context.set_options` returns the new options value. """ context = Context(SSLv23_METHOD) options = context.set_options(OP_NO_SSLv2) assert options & OP_NO_SSLv2 == OP_NO_SSLv2 def test_set_mode_wrong_args(self): """ `Context.set_mode` raises `TypeError` if called with a non-`int` argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_mode(None) def test_set_mode(self): """ `Context.set_mode` accepts a mode bitvector and returns the newly set mode. """ context = Context(SSLv23_METHOD) assert MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS) def test_set_timeout_wrong_args(self): """ `Context.set_timeout` raises `TypeError` if called with a non-`int` argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_timeout(None) def test_timeout(self): """ `Context.set_timeout` sets the session timeout for all connections created using the context object. `Context.get_timeout` retrieves this value. """ context = Context(SSLv23_METHOD) context.set_timeout(1234) assert context.get_timeout() == 1234 def test_set_verify_depth_wrong_args(self): """ `Context.set_verify_depth` raises `TypeError` if called with a non-`int` argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_verify_depth(None) def test_verify_depth(self): """ `Context.set_verify_depth` sets the number of certificates in a chain to follow before giving up. The value can be retrieved with `Context.get_verify_depth`. """ context = Context(SSLv23_METHOD) context.set_verify_depth(11) assert context.get_verify_depth() == 11 def _write_encrypted_pem(self, passphrase, tmpfile): """ Write a new private key out to a new file, encrypted using the given passphrase. Return the path to the new file. """ key = PKey() key.generate_key(TYPE_RSA, 1024) pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase) with open(tmpfile, "w") as fObj: fObj.write(pem.decode("ascii")) return tmpfile def test_set_passwd_cb_wrong_args(self): """ `Context.set_passwd_cb` raises `TypeError` if called with a non-callable first argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_passwd_cb(None) def test_set_passwd_cb(self, tmpfile): """ `Context.set_passwd_cb` accepts a callable which will be invoked when a private key is loaded from an encrypted PEM. """ passphrase = b"foobar" pemFile = self._write_encrypted_pem(passphrase, tmpfile) calledWith = [] def passphraseCallback(maxlen, verify, extra): calledWith.append((maxlen, verify, extra)) return passphrase context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) context.use_privatekey_file(pemFile) assert len(calledWith) == 1 assert isinstance(calledWith[0][0], int) assert isinstance(calledWith[0][1], int) assert calledWith[0][2] is None def test_passwd_callback_exception(self, tmpfile): """ `Context.use_privatekey_file` propagates any exception raised by the passphrase callback. """ pemFile = self._write_encrypted_pem(b"monkeys are nice", tmpfile) def passphraseCallback(maxlen, verify, extra): raise RuntimeError("Sorry, I am a fail.") context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(RuntimeError): context.use_privatekey_file(pemFile) def test_passwd_callback_false(self, tmpfile): """ `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` if the passphrase callback returns a false value. """ pemFile = self._write_encrypted_pem(b"monkeys are nice", tmpfile) def passphraseCallback(maxlen, verify, extra): return b"" context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(Error): context.use_privatekey_file(pemFile) def test_passwd_callback_non_string(self, tmpfile): """ `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` if the passphrase callback returns a true non-string value. """ pemFile = self._write_encrypted_pem(b"monkeys are nice", tmpfile) def passphraseCallback(maxlen, verify, extra): return 10 context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) # TODO: Surely this is the wrong error? with pytest.raises(ValueError): context.use_privatekey_file(pemFile) def test_passwd_callback_too_long(self, tmpfile): """ If the passphrase returned by the passphrase callback returns a string longer than the indicated maximum length, it is truncated. """ # A priori knowledge! passphrase = b"x" * 1024 pemFile = self._write_encrypted_pem(passphrase, tmpfile) def passphraseCallback(maxlen, verify, extra): assert maxlen == 1024 return passphrase + b"y" context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) # This shall succeed because the truncated result is the correct # passphrase. context.use_privatekey_file(pemFile) def test_set_info_callback(self): """ `Context.set_info_callback` accepts a callable which will be invoked when certain information about an SSL connection is available. """ (server, client) = socket_pair() clientSSL = Connection(Context(SSLv23_METHOD), client) clientSSL.set_connect_state() called = [] def info(conn, where, ret): called.append((conn, where, ret)) context = Context(SSLv23_METHOD) context.set_info_callback(info) context.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) context.use_privatekey(load_privatekey(FILETYPE_PEM, root_key_pem)) serverSSL = Connection(context, server) serverSSL.set_accept_state() handshake(clientSSL, serverSSL) # The callback must always be called with a Connection instance as the # first argument. It would probably be better to split this into # separate tests for client and server side info callbacks so we could # assert it is called with the right Connection instance. It would # also be good to assert *something* about `where` and `ret`. notConnections = [ conn for (conn, where, ret) in called if not isinstance(conn, Connection) ] assert ( [] == notConnections ), "Some info callback arguments were not Connection instances." @pytest.mark.skipif( not getattr(_lib, "Cryptography_HAS_KEYLOG", None), reason="SSL_CTX_set_keylog_callback unavailable", ) def test_set_keylog_callback(self): """ `Context.set_keylog_callback` accepts a callable which will be invoked when key material is generated or received. """ called = [] def keylog(conn, line): called.append((conn, line)) server_context = Context(TLSv1_2_METHOD) server_context.set_keylog_callback(keylog) server_context.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) server_context.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) client_context = Context(SSLv23_METHOD) self._handshake_test(server_context, client_context) assert called assert all(isinstance(conn, Connection) for conn, line in called) assert all(b"CLIENT_RANDOM" in line for conn, line in called) def test_set_proto_version(self): if OP_NO_TLSv1_3 is None: high_version = TLS1_2_VERSION low_version = TLS1_1_VERSION else: high_version = TLS1_3_VERSION low_version = TLS1_2_VERSION server_context = Context(TLS_METHOD) server_context.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) server_context.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) server_context.set_min_proto_version(high_version) client_context = Context(TLS_METHOD) client_context.set_max_proto_version(low_version) with pytest.raises(Error, match="unsupported protocol"): self._handshake_test(server_context, client_context) client_context.set_max_proto_version(0) self._handshake_test(server_context, client_context) def _load_verify_locations_test(self, *args): """ Create a client context which will verify the peer certificate and call its `load_verify_locations` method with the given arguments. Then connect it to a server and ensure that the handshake succeeds. """ (server, client) = socket_pair() clientContext = Context(SSLv23_METHOD) clientContext.load_verify_locations(*args) # Require that the server certificate verify properly or the # connection will fail. clientContext.set_verify( VERIFY_PEER, lambda conn, cert, errno, depth, preverify_ok: preverify_ok, ) clientSSL = Connection(clientContext, client) clientSSL.set_connect_state() serverContext = Context(SSLv23_METHOD) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverSSL = Connection(serverContext, server) serverSSL.set_accept_state() # Without load_verify_locations above, the handshake # will fail: # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', # 'certificate verify failed')] handshake(clientSSL, serverSSL) cert = clientSSL.get_peer_certificate() assert cert.get_subject().CN == "Testing Root CA" def _load_verify_cafile(self, cafile): """ Verify that if path to a file containing a certificate is passed to `Context.load_verify_locations` for the ``cafile`` parameter, that certificate is used as a trust root for the purposes of verifying connections created using that `Context`. """ with open(cafile, "w") as fObj: fObj.write(root_cert_pem.decode("ascii")) self._load_verify_locations_test(cafile) def test_load_verify_bytes_cafile(self, tmpfile): """ `Context.load_verify_locations` accepts a file name as a `bytes` instance and uses the certificates within for verification purposes. """ cafile = tmpfile + NON_ASCII.encode(getfilesystemencoding()) self._load_verify_cafile(cafile) def test_load_verify_unicode_cafile(self, tmpfile): """ `Context.load_verify_locations` accepts a file name as a `unicode` instance and uses the certificates within for verification purposes. """ self._load_verify_cafile( tmpfile.decode(getfilesystemencoding()) + NON_ASCII ) def test_load_verify_invalid_file(self, tmpfile): """ `Context.load_verify_locations` raises `Error` when passed a non-existent cafile. """ clientContext = Context(SSLv23_METHOD) with pytest.raises(Error): clientContext.load_verify_locations(tmpfile) def _load_verify_directory_locations_capath(self, capath): """ Verify that if path to a directory containing certificate files is passed to ``Context.load_verify_locations`` for the ``capath`` parameter, those certificates are used as trust roots for the purposes of verifying connections created using that ``Context``. """ makedirs(capath) # Hash values computed manually with c_rehash to avoid depending on # c_rehash in the test suite. One is from OpenSSL 0.9.8, the other # from OpenSSL 1.0.0. for name in [b"c7adac82.0", b"c3705638.0"]: cafile = join_bytes_or_unicode(capath, name) with open(cafile, "w") as fObj: fObj.write(root_cert_pem.decode("ascii")) self._load_verify_locations_test(None, capath) @pytest.mark.parametrize( "pathtype", [ "ascii_path", pytest.param( "unicode_path", marks=pytest.mark.skipif( platform == "win32", reason="Unicode paths not supported on Windows", ), ), ], ) @pytest.mark.parametrize("argtype", ["bytes_arg", "unicode_arg"]) def test_load_verify_directory_capath(self, pathtype, argtype, tmpfile): """ `Context.load_verify_locations` accepts a directory name as a `bytes` instance and uses the certificates within for verification purposes. """ if pathtype == "unicode_path": tmpfile += NON_ASCII.encode(getfilesystemencoding()) if argtype == "unicode_arg": tmpfile = tmpfile.decode(getfilesystemencoding()) self._load_verify_directory_locations_capath(tmpfile) def test_load_verify_locations_wrong_args(self): """ `Context.load_verify_locations` raises `TypeError` if with non-`str` arguments. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.load_verify_locations(object()) with pytest.raises(TypeError): context.load_verify_locations(object(), object()) @pytest.mark.skipif( not platform.startswith("linux"), reason="Loading fallback paths is a linux-specific behavior to " "accommodate pyca/cryptography manylinux wheels", ) def test_fallback_default_verify_paths(self, monkeypatch): """ Test that we load certificates successfully on linux from the fallback path. To do this we set the _CRYPTOGRAPHY_MANYLINUX_CA_FILE and _CRYPTOGRAPHY_MANYLINUX_CA_DIR vars to be equal to whatever the current OpenSSL default is and we disable SSL_CTX_SET_default_verify_paths so that it can't find certs unless it loads via fallback. """ context = Context(SSLv23_METHOD) monkeypatch.setattr( _lib, "SSL_CTX_set_default_verify_paths", lambda x: 1 ) monkeypatch.setattr( SSL, "_CRYPTOGRAPHY_MANYLINUX_CA_FILE", _ffi.string(_lib.X509_get_default_cert_file()), ) monkeypatch.setattr( SSL, "_CRYPTOGRAPHY_MANYLINUX_CA_DIR", _ffi.string(_lib.X509_get_default_cert_dir()), ) context.set_default_verify_paths() store = context.get_cert_store() sk_obj = _lib.X509_STORE_get0_objects(store._store) assert sk_obj != _ffi.NULL num = _lib.sk_X509_OBJECT_num(sk_obj) assert num != 0 def test_check_env_vars(self, monkeypatch): """ Test that we return True/False appropriately if the env vars are set. """ context = Context(SSLv23_METHOD) dir_var = "CUSTOM_DIR_VAR" file_var = "CUSTOM_FILE_VAR" assert context._check_env_vars_set(dir_var, file_var) is False monkeypatch.setenv(dir_var, "value") monkeypatch.setenv(file_var, "value") assert context._check_env_vars_set(dir_var, file_var) is True assert context._check_env_vars_set(dir_var, file_var) is True def test_verify_no_fallback_if_env_vars_set(self, monkeypatch): """ Test that we don't use the fallback path if env vars are set. """ context = Context(SSLv23_METHOD) monkeypatch.setattr( _lib, "SSL_CTX_set_default_verify_paths", lambda x: 1 ) dir_env_var = _ffi.string(_lib.X509_get_default_cert_dir_env()).decode( "ascii" ) file_env_var = _ffi.string( _lib.X509_get_default_cert_file_env() ).decode("ascii") monkeypatch.setenv(dir_env_var, "value") monkeypatch.setenv(file_env_var, "value") context.set_default_verify_paths() monkeypatch.setattr( context, "_fallback_default_verify_paths", raiser(SystemError) ) context.set_default_verify_paths() @pytest.mark.skipif( platform == "win32", reason="set_default_verify_paths appears not to work on Windows. " "See LP#404343 and LP#404344.", ) def test_set_default_verify_paths(self): """ `Context.set_default_verify_paths` causes the platform-specific CA certificate locations to be used for verification purposes. """ # Testing this requires a server with a certificate signed by one # of the CAs in the platform CA location. Getting one of those # costs money. Fortunately (or unfortunately, depending on your # perspective), it's easy to think of a public server on the # internet which has such a certificate. Connecting to the network # in a unit test is bad, but it's the only way I can think of to # really test this. -exarkun context = Context(SSLv23_METHOD) context.set_default_verify_paths() context.set_verify( VERIFY_PEER, lambda conn, cert, errno, depth, preverify_ok: preverify_ok, ) client = socket_any_family() try: client.connect(("encrypted.google.com", 443)) except gaierror: pytest.skip("cannot connect to encrypted.google.com") clientSSL = Connection(context, client) clientSSL.set_connect_state() clientSSL.set_tlsext_host_name(b"encrypted.google.com") clientSSL.do_handshake() clientSSL.send(b"GET / HTTP/1.0\r\n\r\n") assert clientSSL.recv(1024) def test_fallback_path_is_not_file_or_dir(self): """ Test that when passed empty arrays or paths that do not exist no errors are raised. """ context = Context(SSLv23_METHOD) context._fallback_default_verify_paths([], []) context._fallback_default_verify_paths(["/not/a/file"], ["/not/a/dir"]) def test_add_extra_chain_cert_invalid_cert(self): """ `Context.add_extra_chain_cert` raises `TypeError` if called with an object which is not an instance of `X509`. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.add_extra_chain_cert(object()) def _handshake_test(self, serverContext, clientContext): """ Verify that a client and server created with the given contexts can successfully handshake and communicate. """ serverSocket, clientSocket = socket_pair() with serverSocket, clientSocket: server = Connection(serverContext, serverSocket) server.set_accept_state() client = Connection(clientContext, clientSocket) client.set_connect_state() # Make them talk to each other. for _ in range(3): for s in [client, server]: try: s.do_handshake() except WantReadError: select.select([client, server], [], []) def test_set_verify_callback_connection_argument(self): """ The first argument passed to the verify callback is the `Connection` instance for which verification is taking place. """ serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) serverConnection = Connection(serverContext, None) class VerifyCallback: def callback(self, connection, *args): self.connection = connection return 1 verify = VerifyCallback() clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_PEER, verify.callback) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() handshake_in_memory(clientConnection, serverConnection) assert verify.connection is clientConnection def test_x509_in_verify_works(self): """ We had a bug where the X509 cert instantiated in the callback wrapper didn't __init__ so it was missing objects needed when calling get_subject. This test sets up a handshake where we call get_subject on the cert provided to the verify callback. """ serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) serverConnection = Connection(serverContext, None) def verify_cb_get_subject(conn, cert, errnum, depth, ok): assert cert.get_subject() return 1 clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_PEER, verify_cb_get_subject) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() handshake_in_memory(clientConnection, serverConnection) def test_set_verify_callback_exception(self): """ If the verify callback passed to `Context.set_verify` raises an exception, verification fails and the exception is propagated to the caller of `Connection.do_handshake`. """ serverContext = Context(TLSv1_2_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) clientContext = Context(TLSv1_2_METHOD) def verify_callback(*args): raise Exception("silly verify failure") clientContext.set_verify(VERIFY_PEER, verify_callback) with pytest.raises(Exception) as exc: self._handshake_test(serverContext, clientContext) assert "silly verify failure" == str(exc.value) def test_set_verify_callback_reference(self): """ If the verify callback passed to `Context.set_verify` is set multiple times, the pointers to the old call functions should not be dangling and trigger a segfault. """ serverContext = Context(TLSv1_2_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) clientContext = Context(TLSv1_2_METHOD) clients = [] for i in range(5): def verify_callback(*args): return True serverSocket, clientSocket = socket_pair() client = Connection(clientContext, clientSocket) clients.append((serverSocket, client)) clientContext.set_verify(VERIFY_PEER, verify_callback) gc.collect() # Make them talk to each other. for serverSocket, client in clients: server = Connection(serverContext, serverSocket) server.set_accept_state() client.set_connect_state() for _ in range(5): for s in [client, server]: try: s.do_handshake() except WantReadError: pass @pytest.mark.parametrize("mode", [SSL.VERIFY_PEER, SSL.VERIFY_NONE]) def test_set_verify_default_callback(self, mode): """ If the verify callback is omitted, the preverify value is used. """ serverContext = Context(TLSv1_2_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, root_key_pem) ) serverContext.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) clientContext = Context(TLSv1_2_METHOD) clientContext.set_verify(mode, None) if mode == SSL.VERIFY_PEER: with pytest.raises(Exception) as exc: self._handshake_test(serverContext, clientContext) assert "certificate verify failed" in str(exc.value) else: self._handshake_test(serverContext, clientContext) def test_add_extra_chain_cert(self, tmpdir): """ `Context.add_extra_chain_cert` accepts an `X509` instance to add to the certificate chain. See `_create_certificate_chain` for the details of the certificate chain tested. The chain is tested by starting a server with scert and connecting to it with a client which trusts cacert and requires verification to succeed. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain # Dump the CA certificate to a file because that's the only way to load # it as a trusted CA in the client context. for cert, name in [ (cacert, "ca.pem"), (icert, "i.pem"), (scert, "s.pem"), ]: with tmpdir.join(name).open("w") as f: f.write(dump_certificate(FILETYPE_PEM, cert).decode("ascii")) for key, name in [(cakey, "ca.key"), (ikey, "i.key"), (skey, "s.key")]: with tmpdir.join(name).open("w") as f: f.write(dump_privatekey(FILETYPE_PEM, key).decode("ascii")) # Create the server context serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) # The client already has cacert, we only need to give them icert. serverContext.add_extra_chain_cert(icert) # Create the client clientContext = Context(SSLv23_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb ) clientContext.load_verify_locations(str(tmpdir.join("ca.pem"))) # Try it out. self._handshake_test(serverContext, clientContext) def _use_certificate_chain_file_test(self, certdir): """ Verify that `Context.use_certificate_chain_file` reads a certificate chain from a specified file. The chain is tested by starting a server with scert and connecting to it with a client which trusts cacert and requires verification to succeed. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain makedirs(certdir) chainFile = join_bytes_or_unicode(certdir, "chain.pem") caFile = join_bytes_or_unicode(certdir, "ca.pem") # Write out the chain file. with open(chainFile, "wb") as fObj: # Most specific to least general. fObj.write(dump_certificate(FILETYPE_PEM, scert)) fObj.write(dump_certificate(FILETYPE_PEM, icert)) fObj.write(dump_certificate(FILETYPE_PEM, cacert)) with open(caFile, "w") as fObj: fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode("ascii")) serverContext = Context(SSLv23_METHOD) serverContext.use_certificate_chain_file(chainFile) serverContext.use_privatekey(skey) clientContext = Context(SSLv23_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb ) clientContext.load_verify_locations(caFile) self._handshake_test(serverContext, clientContext) def test_use_certificate_chain_file_bytes(self, tmpfile): """ ``Context.use_certificate_chain_file`` accepts the name of a file (as an instance of ``bytes``) to specify additional certificates to use to construct and verify a trust chain. """ self._use_certificate_chain_file_test( tmpfile + NON_ASCII.encode(getfilesystemencoding()) ) def test_use_certificate_chain_file_unicode(self, tmpfile): """ ``Context.use_certificate_chain_file`` accepts the name of a file (as an instance of ``unicode``) to specify additional certificates to use to construct and verify a trust chain. """ self._use_certificate_chain_file_test( tmpfile.decode(getfilesystemencoding()) + NON_ASCII ) def test_use_certificate_chain_file_wrong_args(self): """ `Context.use_certificate_chain_file` raises `TypeError` if passed a non-byte string single argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.use_certificate_chain_file(object()) def test_use_certificate_chain_file_missing_file(self, tmpfile): """ `Context.use_certificate_chain_file` raises `OpenSSL.SSL.Error` when passed a bad chain file name (for example, the name of a file which does not exist). """ context = Context(SSLv23_METHOD) with pytest.raises(Error): context.use_certificate_chain_file(tmpfile) def test_set_verify_mode(self): """ `Context.get_verify_mode` returns the verify mode flags previously passed to `Context.set_verify`. """ context = Context(SSLv23_METHOD) assert context.get_verify_mode() == 0 context.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE) assert context.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE) @pytest.mark.parametrize("mode", [None, 1.0, object(), "mode"]) def test_set_verify_wrong_mode_arg(self, mode): """ `Context.set_verify` raises `TypeError` if the first argument is not an integer. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_verify(mode=mode) @pytest.mark.parametrize("callback", [1.0, "mode", ("foo", "bar")]) def test_set_verify_wrong_callable_arg(self, callback): """ `Context.set_verify` raises `TypeError` if the second argument is not callable. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_verify(mode=VERIFY_PEER, callback=callback) def test_load_tmp_dh_wrong_args(self): """ `Context.load_tmp_dh` raises `TypeError` if called with a non-`str` argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.load_tmp_dh(object()) def test_load_tmp_dh_missing_file(self): """ `Context.load_tmp_dh` raises `OpenSSL.SSL.Error` if the specified file does not exist. """ context = Context(SSLv23_METHOD) with pytest.raises(Error): context.load_tmp_dh(b"hello") def _load_tmp_dh_test(self, dhfilename): """ Verify that calling ``Context.load_tmp_dh`` with the given filename does not raise an exception. """ context = Context(SSLv23_METHOD) with open(dhfilename, "w") as dhfile: dhfile.write(dhparam) context.load_tmp_dh(dhfilename) def test_load_tmp_dh_bytes(self, tmpfile): """ `Context.load_tmp_dh` loads Diffie-Hellman parameters from the specified file (given as ``bytes``). """ self._load_tmp_dh_test( tmpfile + NON_ASCII.encode(getfilesystemencoding()), ) def test_load_tmp_dh_unicode(self, tmpfile): """ `Context.load_tmp_dh` loads Diffie-Hellman parameters from the specified file (given as ``unicode``). """ self._load_tmp_dh_test( tmpfile.decode(getfilesystemencoding()) + NON_ASCII, ) def test_set_tmp_ecdh(self): """ `Context.set_tmp_ecdh` sets the elliptic curve for Diffie-Hellman to the specified curve. """ context = Context(SSLv23_METHOD) for curve in get_elliptic_curves(): if curve.name.startswith("Oakley-"): # Setting Oakley-EC2N-4 and Oakley-EC2N-3 adds # ('bignum routines', 'BN_mod_inverse', 'no inverse') to the # error queue on OpenSSL 1.0.2. continue # The only easily "assertable" thing is that it does not raise an # exception. context.set_tmp_ecdh(curve) def test_set_session_cache_mode_wrong_args(self): """ `Context.set_session_cache_mode` raises `TypeError` if called with a non-integer argument. called with other than one integer argument. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_session_cache_mode(object()) def test_session_cache_mode(self): """ `Context.set_session_cache_mode` specifies how sessions are cached. The setting can be retrieved via `Context.get_session_cache_mode`. """ context = Context(SSLv23_METHOD) context.set_session_cache_mode(SESS_CACHE_OFF) off = context.set_session_cache_mode(SESS_CACHE_BOTH) assert SESS_CACHE_OFF == off assert SESS_CACHE_BOTH == context.get_session_cache_mode() def test_get_cert_store(self): """ `Context.get_cert_store` returns a `X509Store` instance. """ context = Context(SSLv23_METHOD) store = context.get_cert_store() assert isinstance(store, X509Store) def test_set_tlsext_use_srtp_not_bytes(self): """ `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material. It raises a TypeError if the list of profiles is not a byte string. """ context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_tlsext_use_srtp("SRTP_AES128_CM_SHA1_80") def test_set_tlsext_use_srtp_invalid_profile(self): """ `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material. It raises an Error if the call to OpenSSL fails. """ context = Context(SSLv23_METHOD) with pytest.raises(Error): context.set_tlsext_use_srtp(b"SRTP_BOGUS") def test_set_tlsext_use_srtp_valid(self): """ `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material. It does not return anything. """ context = Context(SSLv23_METHOD) assert context.set_tlsext_use_srtp(b"SRTP_AES128_CM_SHA1_80") is None class TestServerNameCallback: """ Tests for `Context.set_tlsext_servername_callback` and its interaction with `Connection`. """ def test_old_callback_forgotten(self): """ If `Context.set_tlsext_servername_callback` is used to specify a new callback, the one it replaces is dereferenced. """ def callback(connection): # pragma: no cover pass def replacement(connection): # pragma: no cover pass context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(callback) tracker = ref(callback) del callback context.set_tlsext_servername_callback(replacement) # One run of the garbage collector happens to work on CPython. PyPy # doesn't collect the underlying object until a second run for whatever # reason. That's fine, it still demonstrates our code has properly # dropped the reference. collect() collect() callback = tracker() if callback is not None: referrers = get_referrers(callback) if len(referrers) > 1: # pragma: nocover pytest.fail(f"Some references remain: {referrers!r}") def test_no_servername(self): """ When a client specifies no server name, the callback passed to `Context.set_tlsext_servername_callback` is invoked and the result of `Connection.get_servername` is `None`. """ args = [] def servername(conn): args.append((conn, conn.get_servername())) context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(servername) # Lose our reference to it. The Context is responsible for keeping it # alive now. del servername collect() # Necessary to actually accept the connection context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(context, None) server.set_accept_state() client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() interact_in_memory(server, client) assert args == [(server, None)] def test_servername(self): """ When a client specifies a server name in its hello message, the callback passed to `Contexts.set_tlsext_servername_callback` is invoked and the result of `Connection.get_servername` is that server name. """ args = [] def servername(conn): args.append((conn, conn.get_servername())) context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(servername) # Necessary to actually accept the connection context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(context, None) server.set_accept_state() client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() client.set_tlsext_host_name(b"foo1.example.com") interact_in_memory(server, client) assert args == [(server, b"foo1.example.com")] class TestApplicationLayerProtoNegotiation: """ Tests for ALPN in PyOpenSSL. """ def test_alpn_success(self): """ Clients and servers that agree on the negotiated ALPN protocol can correct establish a connection, and the agreed protocol is reported by the connections. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b"spdy/2" client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() interact_in_memory(server, client) assert select_args == [(server, [b"http/1.1", b"spdy/2"])] assert server.get_alpn_proto_negotiated() == b"spdy/2" assert client.get_alpn_proto_negotiated() == b"spdy/2" def test_alpn_call_failure(self): """ SSL_CTX_set_alpn_protos does not like to be called with an empty protocols list. Ensure that we produce a user-visible error. """ context = Context(SSLv23_METHOD) with pytest.raises(ValueError): context.set_alpn_protos([]) def test_alpn_set_on_connection(self): """ The same as test_alpn_success, but setting the ALPN protocols on the connection rather than the context. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b"spdy/2" # Setup the client context but don't set any ALPN protocols. client_context = Context(SSLv23_METHOD) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() # Set the ALPN protocols on the client connection. client = Connection(client_context, None) client.set_alpn_protos([b"http/1.1", b"spdy/2"]) client.set_connect_state() interact_in_memory(server, client) assert select_args == [(server, [b"http/1.1", b"spdy/2"])] assert server.get_alpn_proto_negotiated() == b"spdy/2" assert client.get_alpn_proto_negotiated() == b"spdy/2" def test_alpn_server_fail(self): """ When clients and servers cannot agree on what protocol to use next the TLS connection does not get established. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b"" client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # If the client doesn't return anything, the connection will fail. with pytest.raises(Error): interact_in_memory(server, client) assert select_args == [(server, [b"http/1.1", b"spdy/2"])] def test_alpn_no_server_overlap(self): """ A server can allow a TLS handshake to complete without agreeing to an application protocol by returning ``NO_OVERLAPPING_PROTOCOLS``. """ refusal_args = [] def refusal(conn, options): refusal_args.append((conn, options)) return NO_OVERLAPPING_PROTOCOLS client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(refusal) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # Do the dance. interact_in_memory(server, client) assert refusal_args == [(server, [b"http/1.1", b"spdy/2"])] assert client.get_alpn_proto_negotiated() == b"" def test_alpn_select_cb_returns_invalid_value(self): """ If the ALPN selection callback returns anything other than a bytestring or ``NO_OVERLAPPING_PROTOCOLS``, a :py:exc:`TypeError` is raised. """ invalid_cb_args = [] def invalid_cb(conn, options): invalid_cb_args.append((conn, options)) return "can't return unicode" client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(invalid_cb) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # Do the dance. with pytest.raises(TypeError): interact_in_memory(server, client) assert invalid_cb_args == [(server, [b"http/1.1", b"spdy/2"])] assert client.get_alpn_proto_negotiated() == b"" def test_alpn_no_server(self): """ When clients and servers cannot agree on what protocol to use next because the server doesn't offer ALPN, no protocol is negotiated. """ client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # Do the dance. interact_in_memory(server, client) assert client.get_alpn_proto_negotiated() == b"" def test_alpn_callback_exception(self): """ We can handle exceptions in the ALPN select callback. """ select_args = [] def select(conn, options): select_args.append((conn, options)) raise TypeError() client_context = Context(SSLv23_METHOD) client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) server_context = Context(SSLv23_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() with pytest.raises(TypeError): interact_in_memory(server, client) assert select_args == [(server, [b"http/1.1", b"spdy/2"])] class TestSession: """ Unit tests for :py:obj:`OpenSSL.SSL.Session`. """ def test_construction(self): """ :py:class:`Session` can be constructed with no arguments, creating a new instance of that type. """ new_session = Session() assert isinstance(new_session, Session) @pytest.fixture(params=["context", "connection"]) def ctx_or_conn(request) -> Union[Context, Connection]: ctx = Context(SSLv23_METHOD) if request.param == "context": return ctx else: return Connection(ctx, None) class TestContextConnection: """ Unit test for methods that are exposed both by Connection and Context objects. """ def test_use_privatekey(self, ctx_or_conn): """ `use_privatekey` takes an `OpenSSL.crypto.PKey` instance. """ key = PKey() key.generate_key(TYPE_RSA, 1024) ctx_or_conn.use_privatekey(key) with pytest.raises(TypeError): ctx_or_conn.use_privatekey("") def test_use_privatekey_wrong_key(self, ctx_or_conn): """ `use_privatekey` raises `OpenSSL.SSL.Error` when passed a `OpenSSL.crypto.PKey` instance which has not been initialized. """ key = PKey() key.generate_key(TYPE_RSA, 1024) ctx_or_conn.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) with pytest.raises(Error): ctx_or_conn.use_privatekey(key) def test_use_certificate(self, ctx_or_conn): """ `use_certificate` sets the certificate which will be used to identify connections created using the context. """ # TODO # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as # long as check_privatekey works right we're good... ctx_or_conn.use_certificate( load_certificate(FILETYPE_PEM, root_cert_pem) ) def test_use_certificate_wrong_args(self, ctx_or_conn): """ `use_certificate_wrong_args` raises `TypeError` when not passed exactly one `OpenSSL.crypto.X509` instance as an argument. """ with pytest.raises(TypeError): ctx_or_conn.use_certificate("hello, world") def test_use_certificate_uninitialized(self, ctx_or_conn): """ `use_certificate` raises `OpenSSL.SSL.Error` when passed a `OpenSSL.crypto.X509` instance which has not been initialized (ie, which does not actually have any certificate data). """ with pytest.raises(Error): ctx_or_conn.use_certificate(X509()) class TestConnection: """ Unit tests for `OpenSSL.SSL.Connection`. """ # XXX get_peer_certificate -> None # XXX sock_shutdown # XXX master_key -> TypeError # XXX server_random -> TypeError # XXX connect -> TypeError # XXX connect_ex -> TypeError # XXX set_connect_state -> TypeError # XXX set_accept_state -> TypeError # XXX do_handshake -> TypeError # XXX bio_read -> TypeError # XXX recv -> TypeError # XXX send -> TypeError # XXX bio_write -> TypeError def test_type(self): """ `Connection` can be used to create instances of that type. """ ctx = Context(SSLv23_METHOD) assert is_consistent_type(Connection, "Connection", ctx, None) @pytest.mark.parametrize("bad_context", [object(), "context", None, 1]) def test_wrong_args(self, bad_context): """ `Connection.__init__` raises `TypeError` if called with a non-`Context` instance argument. """ with pytest.raises(TypeError): Connection(bad_context) @pytest.mark.parametrize("bad_bio", [object(), None, 1, [1, 2, 3]]) def test_bio_write_wrong_args(self, bad_bio): """ `Connection.bio_write` raises `TypeError` if called with a non-bytes (or text) argument. """ context = Context(SSLv23_METHOD) connection = Connection(context, None) with pytest.raises(TypeError): connection.bio_write(bad_bio) def test_bio_write(self): """ `Connection.bio_write` does not raise if called with bytes or bytearray, warns if called with text. """ context = Context(SSLv23_METHOD) connection = Connection(context, None) connection.bio_write(b"xy") connection.bio_write(bytearray(b"za")) with pytest.warns(DeprecationWarning): connection.bio_write("deprecated") def test_get_context(self): """ `Connection.get_context` returns the `Context` instance used to construct the `Connection` instance. """ context = Context(SSLv23_METHOD) connection = Connection(context, None) assert connection.get_context() is context def test_set_context_wrong_args(self): """ `Connection.set_context` raises `TypeError` if called with a non-`Context` instance argument. """ ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_context(object()) with pytest.raises(TypeError): connection.set_context("hello") with pytest.raises(TypeError): connection.set_context(1) assert ctx is connection.get_context() def test_set_context(self): """ `Connection.set_context` specifies a new `Context` instance to be used for the connection. """ original = Context(SSLv23_METHOD) replacement = Context(SSLv23_METHOD) connection = Connection(original, None) connection.set_context(replacement) assert replacement is connection.get_context() # Lose our references to the contexts, just in case the Connection # isn't properly managing its own contributions to their reference # counts. del original, replacement collect() def test_set_tlsext_host_name_wrong_args(self): """ If `Connection.set_tlsext_host_name` is called with a non-byte string argument or a byte string with an embedded NUL, `TypeError` is raised. """ conn = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): conn.set_tlsext_host_name(object()) with pytest.raises(TypeError): conn.set_tlsext_host_name(b"with\0null") with pytest.raises(TypeError): conn.set_tlsext_host_name(b"example.com".decode("ascii")) def test_pending(self): """ `Connection.pending` returns the number of bytes available for immediate read. """ connection = Connection(Context(SSLv23_METHOD), None) assert connection.pending() == 0 def test_peek(self): """ `Connection.recv` peeks into the connection if `socket.MSG_PEEK` is passed. """ server, client = loopback() server.send(b"xy") assert client.recv(2, MSG_PEEK) == b"xy" assert client.recv(2, MSG_PEEK) == b"xy" assert client.recv(2) == b"xy" def test_connect_wrong_args(self): """ `Connection.connect` raises `TypeError` if called with a non-address argument. """ connection = Connection(Context(SSLv23_METHOD), socket_any_family()) with pytest.raises(TypeError): connection.connect(None) def test_connect_refused(self): """ `Connection.connect` raises `socket.error` if the underlying socket connect method raises it. """ client = socket_any_family() context = Context(SSLv23_METHOD) clientSSL = Connection(context, client) # pytest.raises here doesn't work because of a bug in py.test on Python # 2.6: https://github.com/pytest-dev/pytest/issues/988 try: clientSSL.connect((loopback_address(client), 1)) except OSError as e: exc = e assert exc.args[0] == ECONNREFUSED def test_connect(self): """ `Connection.connect` establishes a connection to the specified address. """ port = socket_any_family() port.bind(("", 0)) port.listen(3) clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) clientSSL.connect((loopback_address(port), port.getsockname()[1])) # XXX An assertion? Or something? def test_connect_ex(self): """ If there is a connection error, `Connection.connect_ex` returns the errno instead of raising an exception. """ port = socket_any_family() port.bind(("", 0)) port.listen(3) clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) clientSSL.setblocking(False) result = clientSSL.connect_ex(port.getsockname()) expected = (EINPROGRESS, EWOULDBLOCK) assert result in expected def test_accept(self): """ `Connection.accept` accepts a pending connection attempt and returns a tuple of a new `Connection` (the accepted client) and the address the connection originated from. """ ctx = Context(SSLv23_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) port = socket_any_family() portSSL = Connection(ctx, port) portSSL.bind(("", 0)) portSSL.listen(3) clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) # Calling portSSL.getsockname() here to get the server IP address # sounds great, but frequently fails on Windows. clientSSL.connect((loopback_address(port), portSSL.getsockname()[1])) serverSSL, address = portSSL.accept() assert isinstance(serverSSL, Connection) assert serverSSL.get_context() is ctx assert address == clientSSL.getsockname() def test_shutdown_wrong_args(self): """ `Connection.set_shutdown` raises `TypeError` if called with arguments other than integers. """ connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.set_shutdown(None) def test_shutdown(self): """ `Connection.shutdown` performs an SSL-level connection shutdown. """ server, client = loopback() assert not server.shutdown() assert server.get_shutdown() == SENT_SHUTDOWN with pytest.raises(ZeroReturnError): client.recv(1024) assert client.get_shutdown() == RECEIVED_SHUTDOWN client.shutdown() assert client.get_shutdown() == (SENT_SHUTDOWN | RECEIVED_SHUTDOWN) with pytest.raises(ZeroReturnError): server.recv(1024) assert server.get_shutdown() == (SENT_SHUTDOWN | RECEIVED_SHUTDOWN) def test_shutdown_closed(self): """ If the underlying socket is closed, `Connection.shutdown` propagates the write error from the low level write call. """ server, client = loopback() server.sock_shutdown(2) with pytest.raises(SysCallError) as exc: server.shutdown() if platform == "win32": assert exc.value.args[0] == ESHUTDOWN else: assert exc.value.args[0] == EPIPE def test_shutdown_truncated(self): """ If the underlying connection is truncated, `Connection.shutdown` raises an `Error`. """ server_ctx = Context(SSLv23_METHOD) client_ctx = Context(SSLv23_METHOD) server_ctx.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_ctx.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) server = Connection(server_ctx, None) client = Connection(client_ctx, None) handshake_in_memory(client, server) assert not server.shutdown() with pytest.raises(WantReadError): server.shutdown() server.bio_shutdown() with pytest.raises(Error): server.shutdown() def test_set_shutdown(self): """ `Connection.set_shutdown` sets the state of the SSL connection shutdown process. """ connection = Connection(Context(SSLv23_METHOD), socket_any_family()) connection.set_shutdown(RECEIVED_SHUTDOWN) assert connection.get_shutdown() == RECEIVED_SHUTDOWN def test_state_string(self): """ `Connection.state_string` verbosely describes the current state of the `Connection`. """ server, client = socket_pair() server = loopback_server_factory(server) client = loopback_client_factory(client) assert server.get_state_string() in [ b"before/accept initialization", b"before SSL initialization", ] assert client.get_state_string() in [ b"before/connect initialization", b"before SSL initialization", ] def test_app_data(self): """ Any object can be set as app data by passing it to `Connection.set_app_data` and later retrieved with `Connection.get_app_data`. """ conn = Connection(Context(SSLv23_METHOD), None) assert None is conn.get_app_data() app_data = object() conn.set_app_data(app_data) assert conn.get_app_data() is app_data def test_makefile(self): """ `Connection.makefile` is not implemented and calling that method raises `NotImplementedError`. """ conn = Connection(Context(SSLv23_METHOD), None) with pytest.raises(NotImplementedError): conn.makefile() def test_get_certificate(self): """ `Connection.get_certificate` returns the local certificate. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain context = Context(SSLv23_METHOD) context.use_certificate(scert) client = Connection(context, None) cert = client.get_certificate() assert cert is not None assert "Server Certificate" == cert.get_subject().CN def test_get_certificate_none(self): """ `Connection.get_certificate` returns the local certificate. If there is no certificate, it returns None. """ context = Context(SSLv23_METHOD) client = Connection(context, None) cert = client.get_certificate() assert cert is None def test_get_peer_cert_chain(self): """ `Connection.get_peer_cert_chain` returns a list of certificates which the connected server returned for the certification verification. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) serverContext.add_extra_chain_cert(icert) serverContext.add_extra_chain_cert(cacert) server = Connection(serverContext, None) server.set_accept_state() # Create the client clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_NONE, verify_cb) client = Connection(clientContext, None) client.set_connect_state() interact_in_memory(client, server) chain = client.get_peer_cert_chain() assert len(chain) == 3 assert "Server Certificate" == chain[0].get_subject().CN assert "Intermediate Certificate" == chain[1].get_subject().CN assert "Authority Certificate" == chain[2].get_subject().CN def test_get_peer_cert_chain_none(self): """ `Connection.get_peer_cert_chain` returns `None` if the peer sends no certificate chain. """ ctx = Context(SSLv23_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(ctx, None) server.set_accept_state() client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() interact_in_memory(client, server) assert None is server.get_peer_cert_chain() def test_get_verified_chain(self): """ `Connection.get_verified_chain` returns a list of certificates which the connected server returned for the certification verification. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) serverContext.add_extra_chain_cert(icert) serverContext.add_extra_chain_cert(cacert) server = Connection(serverContext, None) server.set_accept_state() # Create the client clientContext = Context(SSLv23_METHOD) # cacert is self-signed so the client must trust it for verification # to succeed. clientContext.get_cert_store().add_cert(cacert) clientContext.set_verify(VERIFY_PEER, verify_cb) client = Connection(clientContext, None) client.set_connect_state() interact_in_memory(client, server) chain = client.get_verified_chain() assert len(chain) == 3 assert "Server Certificate" == chain[0].get_subject().CN assert "Intermediate Certificate" == chain[1].get_subject().CN assert "Authority Certificate" == chain[2].get_subject().CN def test_get_verified_chain_none(self): """ `Connection.get_verified_chain` returns `None` if the peer sends no certificate chain. """ ctx = Context(SSLv23_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(ctx, None) server.set_accept_state() client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() interact_in_memory(client, server) assert None is server.get_verified_chain() def test_get_verified_chain_unconnected(self): """ `Connection.get_verified_chain` returns `None` when used with an object which has not been connected. """ ctx = Context(SSLv23_METHOD) server = Connection(ctx, None) assert None is server.get_verified_chain() def test_set_verify_overrides_context(self): context = Context(SSLv23_METHOD) context.set_verify(VERIFY_PEER) conn = Connection(context, None) conn.set_verify(VERIFY_NONE) assert context.get_verify_mode() == VERIFY_PEER assert conn.get_verify_mode() == VERIFY_NONE with pytest.raises(TypeError): conn.set_verify(None) with pytest.raises(TypeError): conn.set_verify(VERIFY_PEER, "not a callable") def test_set_verify_callback_reference(self): """ The callback for certificate verification should only be forgotten if the context and all connections created by it do not use it anymore. """ def callback(conn, cert, errnum, depth, ok): # pragma: no cover return ok tracker = ref(callback) context = Context(SSLv23_METHOD) context.set_verify(VERIFY_PEER, callback) del callback conn = Connection(context, None) context.set_verify(VERIFY_NONE) collect() collect() assert tracker() conn.set_verify(VERIFY_PEER, lambda conn, cert, errnum, depth, ok: ok) collect() collect() callback = tracker() if callback is not None: # pragma: nocover referrers = get_referrers(callback) if len(referrers) > 1: pytest.fail(f"Some references remain: {referrers!r}") def test_get_session_unconnected(self): """ `Connection.get_session` returns `None` when used with an object which has not been connected. """ ctx = Context(SSLv23_METHOD) server = Connection(ctx, None) session = server.get_session() assert None is session def test_server_get_session(self): """ On the server side of a connection, `Connection.get_session` returns a `Session` instance representing the SSL session for that connection. """ server, client = loopback() session = server.get_session() assert isinstance(session, Session) def test_client_get_session(self): """ On the client side of a connection, `Connection.get_session` returns a `Session` instance representing the SSL session for that connection. """ server, client = loopback() session = client.get_session() assert isinstance(session, Session) def test_set_session_wrong_args(self): """ `Connection.set_session` raises `TypeError` if called with an object that is not an instance of `Session`. """ ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_session(123) with pytest.raises(TypeError): connection.set_session("hello") with pytest.raises(TypeError): connection.set_session(object()) def test_client_set_session(self): """ `Connection.set_session`, when used prior to a connection being established, accepts a `Session` instance and causes an attempt to re-use the session it represents when the SSL handshake is performed. """ key = load_privatekey(FILETYPE_PEM, server_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) ctx = Context(TLSv1_2_METHOD) ctx.use_privatekey(key) ctx.use_certificate(cert) ctx.set_session_id(b"unity-test") def makeServer(socket): server = Connection(ctx, socket) server.set_accept_state() return server originalServer, originalClient = loopback(server_factory=makeServer) originalSession = originalClient.get_session() def makeClient(socket): client = loopback_client_factory(socket) client.set_session(originalSession) return client resumedServer, resumedClient = loopback( server_factory=makeServer, client_factory=makeClient ) # This is a proxy: in general, we have no access to any unique # identifier for the session (new enough versions of OpenSSL expose # a hash which could be usable, but "new enough" is very, very new). # Instead, exploit the fact that the master key is re-used if the # session is re-used. As long as the master key for the two # connections is the same, the session was re-used! assert originalServer.master_key() == resumedServer.master_key() def test_set_session_wrong_method(self): """ If `Connection.set_session` is passed a `Session` instance associated with a context using a different SSL method than the `Connection` is using, a `OpenSSL.SSL.Error` is raised. """ v1 = TLSv1_2_METHOD v2 = TLSv1_METHOD key = load_privatekey(FILETYPE_PEM, server_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) ctx = Context(v1) ctx.use_privatekey(key) ctx.use_certificate(cert) ctx.set_session_id(b"unity-test") def makeServer(socket): server = Connection(ctx, socket) server.set_accept_state() return server def makeOriginalClient(socket): client = Connection(Context(v1), socket) client.set_connect_state() return client originalServer, originalClient = loopback( server_factory=makeServer, client_factory=makeOriginalClient ) originalSession = originalClient.get_session() def makeClient(socket): # Intentionally use a different, incompatible method here. client = Connection(Context(v2), socket) client.set_connect_state() client.set_session(originalSession) return client with pytest.raises(Error): loopback(client_factory=makeClient, server_factory=makeServer) def test_wantWriteError(self): """ `Connection` methods which generate output raise `OpenSSL.SSL.WantWriteError` if writing to the connection's BIO fail indicating a should-write state. """ client_socket, server_socket = socket_pair() # Fill up the client's send buffer so Connection won't be able to write # anything. Start by sending larger chunks (Windows Socket I/O is slow) # and continue by writing a single byte at a time so we can be sure we # completely fill the buffer. Even though the socket API is allowed to # signal a short write via its return value it seems this doesn't # always happen on all platforms (FreeBSD and OS X particular) for the # very last bit of available buffer space. for msg in [b"x" * 65536, b"x"]: for i in range(1024 * 1024 * 64): try: client_socket.send(msg) except OSError as e: if e.errno == EWOULDBLOCK: break raise # pragma: no cover else: # pragma: no cover pytest.fail( "Failed to fill socket buffer, cannot test BIO want write" ) ctx = Context(SSLv23_METHOD) conn = Connection(ctx, client_socket) # Client's speak first, so make it an SSL client conn.set_connect_state() with pytest.raises(WantWriteError): conn.do_handshake() # XXX want_read def test_get_finished_before_connect(self): """ `Connection.get_finished` returns `None` before TLS handshake is completed. """ ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) assert connection.get_finished() is None def test_get_peer_finished_before_connect(self): """ `Connection.get_peer_finished` returns `None` before TLS handshake is completed. """ ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) assert connection.get_peer_finished() is None def test_get_finished(self): """ `Connection.get_finished` method returns the TLS Finished message send from client, or server. Finished messages are send during TLS handshake. """ server, client = loopback() assert server.get_finished() is not None assert len(server.get_finished()) > 0 def test_get_peer_finished(self): """ `Connection.get_peer_finished` method returns the TLS Finished message received from client, or server. Finished messages are send during TLS handshake. """ server, client = loopback() assert server.get_peer_finished() is not None assert len(server.get_peer_finished()) > 0 def test_tls_finished_message_symmetry(self): """ The TLS Finished message send by server must be the TLS Finished message received by client. The TLS Finished message send by client must be the TLS Finished message received by server. """ server, client = loopback() assert server.get_finished() == client.get_peer_finished() assert client.get_finished() == server.get_peer_finished() def test_get_cipher_name_before_connect(self): """ `Connection.get_cipher_name` returns `None` if no connection has been established. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_name() is None def test_get_cipher_name(self): """ `Connection.get_cipher_name` returns a `unicode` string giving the name of the currently used cipher. """ server, client = loopback() server_cipher_name, client_cipher_name = ( server.get_cipher_name(), client.get_cipher_name(), ) assert isinstance(server_cipher_name, str) assert isinstance(client_cipher_name, str) assert server_cipher_name == client_cipher_name def test_get_cipher_version_before_connect(self): """ `Connection.get_cipher_version` returns `None` if no connection has been established. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_version() is None def test_get_cipher_version(self): """ `Connection.get_cipher_version` returns a `unicode` string giving the protocol name of the currently used cipher. """ server, client = loopback() server_cipher_version, client_cipher_version = ( server.get_cipher_version(), client.get_cipher_version(), ) assert isinstance(server_cipher_version, str) assert isinstance(client_cipher_version, str) assert server_cipher_version == client_cipher_version def test_get_cipher_bits_before_connect(self): """ `Connection.get_cipher_bits` returns `None` if no connection has been established. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_bits() is None def test_get_cipher_bits(self): """ `Connection.get_cipher_bits` returns the number of secret bits of the currently used cipher. """ server, client = loopback() server_cipher_bits, client_cipher_bits = ( server.get_cipher_bits(), client.get_cipher_bits(), ) assert isinstance(server_cipher_bits, int) assert isinstance(client_cipher_bits, int) assert server_cipher_bits == client_cipher_bits def test_get_protocol_version_name(self): """ `Connection.get_protocol_version_name()` returns a string giving the protocol version of the current connection. """ server, client = loopback() client_protocol_version_name = client.get_protocol_version_name() server_protocol_version_name = server.get_protocol_version_name() assert isinstance(server_protocol_version_name, str) assert isinstance(client_protocol_version_name, str) assert server_protocol_version_name == client_protocol_version_name def test_get_protocol_version(self): """ `Connection.get_protocol_version()` returns an integer giving the protocol version of the current connection. """ server, client = loopback() client_protocol_version = client.get_protocol_version() server_protocol_version = server.get_protocol_version() assert isinstance(server_protocol_version, int) assert isinstance(client_protocol_version, int) assert server_protocol_version == client_protocol_version def test_wantReadError(self): """ `Connection.bio_read` raises `OpenSSL.SSL.WantReadError` if there are no bytes available to be read from the BIO. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) with pytest.raises(WantReadError): conn.bio_read(1024) @pytest.mark.parametrize("bufsize", [1.0, None, object(), "bufsize"]) def test_bio_read_wrong_args(self, bufsize): """ `Connection.bio_read` raises `TypeError` if passed a non-integer argument. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) with pytest.raises(TypeError): conn.bio_read(bufsize) def test_buffer_size(self): """ `Connection.bio_read` accepts an integer giving the maximum number of bytes to read and return. """ ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) conn.set_connect_state() try: conn.do_handshake() except WantReadError: pass data = conn.bio_read(2) assert 2 == len(data) class TestConnectionGetCipherList: """ Tests for `Connection.get_cipher_list`. """ def test_result(self): """ `Connection.get_cipher_list` returns a list of `bytes` giving the names of the ciphers which might be used. """ connection = Connection(Context(SSLv23_METHOD), None) ciphers = connection.get_cipher_list() assert isinstance(ciphers, list) for cipher in ciphers: assert isinstance(cipher, str) class VeryLarge(bytes): """ Mock object so that we don't have to allocate 2**31 bytes """ def __len__(self): return 2**31 class TestConnectionSend: """ Tests for `Connection.send`. """ def test_wrong_args(self): """ When called with arguments other than string argument for its first parameter, `Connection.send` raises `TypeError`. """ connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.send(object()) with pytest.raises(TypeError): connection.send([1, 2, 3]) def test_short_bytes(self): """ When passed a short byte string, `Connection.send` transmits all of it and returns the number of bytes sent. """ server, client = loopback() count = server.send(b"xy") assert count == 2 assert client.recv(2) == b"xy" def test_text(self): """ When passed a text, `Connection.send` transmits all of it and returns the number of bytes sent. It also raises a DeprecationWarning. """ server, client = loopback() with pytest.warns(DeprecationWarning) as w: count = server.send(b"xy".decode("ascii")) assert ( f"{WARNING_TYPE_EXPECTED} for buf is no longer accepted, " f"use bytes" ) == str(w[-1].message) assert count == 2 assert client.recv(2) == b"xy" def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, `Connection.send` transmits all of them and returns the number of bytes sent. """ server, client = loopback() count = server.send(memoryview(b"xy")) assert count == 2 assert client.recv(2) == b"xy" def test_short_bytearray(self): """ When passed a short bytearray, `Connection.send` transmits all of it and returns the number of bytes sent. """ server, client = loopback() count = server.send(bytearray(b"xy")) assert count == 2 assert client.recv(2) == b"xy" @pytest.mark.skipif( sys.maxsize < 2**31, reason="sys.maxsize < 2**31 - test requires 64 bit", ) def test_buf_too_large(self): """ When passed a buffer containing >= 2**31 bytes, `Connection.send` bails out as SSL_write only accepts an int for the buffer length. """ connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(ValueError) as exc_info: connection.send(VeryLarge()) exc_info.match(r"Cannot send more than .+ bytes at once") def _make_memoryview(size): """ Create a new ``memoryview`` wrapped around a ``bytearray`` of the given size. """ return memoryview(bytearray(size)) class TestConnectionRecvInto: """ Tests for `Connection.recv_into`. """ def _no_length_test(self, factory): """ Assert that when the given buffer is passed to `Connection.recv_into`, whatever bytes are available to be received that fit into that buffer are written into that buffer. """ output_buffer = factory(5) server, client = loopback() server.send(b"xy") assert client.recv_into(output_buffer) == 2 assert output_buffer == bytearray(b"xy\x00\x00\x00") def test_bytearray_no_length(self): """ `Connection.recv_into` can be passed a `bytearray` instance and data in the receive buffer is written to it. """ self._no_length_test(bytearray) def _respects_length_test(self, factory): """ Assert that when the given buffer is passed to `Connection.recv_into` along with a value for `nbytes` that is less than the size of that buffer, only `nbytes` bytes are written into the buffer. """ output_buffer = factory(10) server, client = loopback() server.send(b"abcdefghij") assert client.recv_into(output_buffer, 5) == 5 assert output_buffer == bytearray(b"abcde\x00\x00\x00\x00\x00") def test_bytearray_respects_length(self): """ When called with a `bytearray` instance, `Connection.recv_into` respects the `nbytes` parameter and doesn't copy in more than that number of bytes. """ self._respects_length_test(bytearray) def _doesnt_overfill_test(self, factory): """ Assert that if there are more bytes available to be read from the receive buffer than would fit into the buffer passed to `Connection.recv_into`, only as many as fit are written into it. """ output_buffer = factory(5) server, client = loopback() server.send(b"abcdefghij") assert client.recv_into(output_buffer) == 5 assert output_buffer == bytearray(b"abcde") rest = client.recv(5) assert b"fghij" == rest def test_bytearray_doesnt_overfill(self): """ When called with a `bytearray` instance, `Connection.recv_into` respects the size of the array and doesn't write more bytes into it than will fit. """ self._doesnt_overfill_test(bytearray) def test_bytearray_really_doesnt_overfill(self): """ When called with a `bytearray` instance and an `nbytes` value that is too large, `Connection.recv_into` respects the size of the array and not the `nbytes` value and doesn't write more bytes into the buffer than will fit. """ self._doesnt_overfill_test(bytearray) def test_peek(self): server, client = loopback() server.send(b"xy") for _ in range(2): output_buffer = bytearray(5) assert client.recv_into(output_buffer, flags=MSG_PEEK) == 2 assert output_buffer == bytearray(b"xy\x00\x00\x00") def test_memoryview_no_length(self): """ `Connection.recv_into` can be passed a `memoryview` instance and data in the receive buffer is written to it. """ self._no_length_test(_make_memoryview) def test_memoryview_respects_length(self): """ When called with a `memoryview` instance, `Connection.recv_into` respects the ``nbytes`` parameter and doesn't copy more than that number of bytes in. """ self._respects_length_test(_make_memoryview) def test_memoryview_doesnt_overfill(self): """ When called with a `memoryview` instance, `Connection.recv_into` respects the size of the array and doesn't write more bytes into it than will fit. """ self._doesnt_overfill_test(_make_memoryview) def test_memoryview_really_doesnt_overfill(self): """ When called with a `memoryview` instance and an `nbytes` value that is too large, `Connection.recv_into` respects the size of the array and not the `nbytes` value and doesn't write more bytes into the buffer than will fit. """ self._doesnt_overfill_test(_make_memoryview) class TestConnectionSendall: """ Tests for `Connection.sendall`. """ def test_wrong_args(self): """ When called with arguments other than a string argument for its first parameter, `Connection.sendall` raises `TypeError`. """ connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.sendall(object()) with pytest.raises(TypeError): connection.sendall([1, 2, 3]) def test_short(self): """ `Connection.sendall` transmits all of the bytes in the string passed to it. """ server, client = loopback() server.sendall(b"x") assert client.recv(1) == b"x" def test_text(self): """ `Connection.sendall` transmits all the content in the string passed to it, raising a DeprecationWarning in case of this being a text. """ server, client = loopback() with pytest.warns(DeprecationWarning) as w: server.sendall(b"x".decode("ascii")) assert ( f"{WARNING_TYPE_EXPECTED} for buf is no longer accepted, " f"use bytes" ) == str(w[-1].message) assert client.recv(1) == b"x" def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, `Connection.sendall` transmits all of them. """ server, client = loopback() server.sendall(memoryview(b"x")) assert client.recv(1) == b"x" def test_long(self): """ `Connection.sendall` transmits all the bytes in the string passed to it even if this requires multiple calls of an underlying write function. """ server, client = loopback() # Should be enough, underlying SSL_write should only do 16k at a time. # On Windows, after 32k of bytes the write will block (forever # - because no one is yet reading). message = b"x" * (1024 * 32 - 1) + b"y" server.sendall(message) accum = [] received = 0 while received < len(message): data = client.recv(1024) accum.append(data) received += len(data) assert message == b"".join(accum) def test_closed(self): """ If the underlying socket is closed, `Connection.sendall` propagates the write error from the low level write call. """ server, client = loopback() server.sock_shutdown(2) with pytest.raises(SysCallError) as err: server.sendall(b"hello, world") if platform == "win32": assert err.value.args[0] == ESHUTDOWN else: assert err.value.args[0] == EPIPE class TestConnectionRenegotiate: """ Tests for SSL renegotiation APIs. """ def test_total_renegotiations(self): """ `Connection.total_renegotiations` returns `0` before any renegotiations have happened. """ connection = Connection(Context(SSLv23_METHOD), None) assert connection.total_renegotiations() == 0 def test_renegotiate(self): """ Go through a complete renegotiation cycle. """ server, client = loopback( lambda s: loopback_server_factory(s, TLSv1_2_METHOD), lambda s: loopback_client_factory(s, TLSv1_2_METHOD), ) server.send(b"hello world") assert b"hello world" == client.recv(len(b"hello world")) assert 0 == server.total_renegotiations() assert False is server.renegotiate_pending() assert True is server.renegotiate() assert True is server.renegotiate_pending() server.setblocking(False) client.setblocking(False) client.do_handshake() server.do_handshake() assert 1 == server.total_renegotiations() while False is server.renegotiate_pending(): pass class TestError: """ Unit tests for `OpenSSL.SSL.Error`. """ def test_type(self): """ `Error` is an exception type. """ assert issubclass(Error, Exception) assert Error.__name__ == "Error" class TestConstants: """ Tests for the values of constants exposed in `OpenSSL.SSL`. These are values defined by OpenSSL intended only to be used as flags to OpenSSL APIs. The only assertions it seems can be made about them is their values. """ @pytest.mark.skipif( OP_NO_QUERY_MTU is None, reason="OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old", ) def test_op_no_query_mtu(self): """ The value of `OpenSSL.SSL.OP_NO_QUERY_MTU` is 0x1000, the value of `SSL_OP_NO_QUERY_MTU` defined by `openssl/ssl.h`. """ assert OP_NO_QUERY_MTU == 0x1000 @pytest.mark.skipif( OP_COOKIE_EXCHANGE is None, reason="OP_COOKIE_EXCHANGE unavailable - " "OpenSSL version may be too old", ) def test_op_cookie_exchange(self): """ The value of `OpenSSL.SSL.OP_COOKIE_EXCHANGE` is 0x2000, the value of `SSL_OP_COOKIE_EXCHANGE` defined by `openssl/ssl.h`. """ assert OP_COOKIE_EXCHANGE == 0x2000 @pytest.mark.skipif( OP_NO_TICKET is None, reason="OP_NO_TICKET unavailable - OpenSSL version may be too old", ) def test_op_no_ticket(self): """ The value of `OpenSSL.SSL.OP_NO_TICKET` is 0x4000, the value of `SSL_OP_NO_TICKET` defined by `openssl/ssl.h`. """ assert OP_NO_TICKET == 0x4000 @pytest.mark.skipif( OP_NO_COMPRESSION is None, reason=( "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" ), ) def test_op_no_compression(self): """ The value of `OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the value of `SSL_OP_NO_COMPRESSION` defined by `openssl/ssl.h`. """ assert OP_NO_COMPRESSION == 0x20000 def test_sess_cache_off(self): """ The value of `OpenSSL.SSL.SESS_CACHE_OFF` 0x0, the value of `SSL_SESS_CACHE_OFF` defined by `openssl/ssl.h`. """ assert 0x0 == SESS_CACHE_OFF def test_sess_cache_client(self): """ The value of `OpenSSL.SSL.SESS_CACHE_CLIENT` 0x1, the value of `SSL_SESS_CACHE_CLIENT` defined by `openssl/ssl.h`. """ assert 0x1 == SESS_CACHE_CLIENT def test_sess_cache_server(self): """ The value of `OpenSSL.SSL.SESS_CACHE_SERVER` 0x2, the value of `SSL_SESS_CACHE_SERVER` defined by `openssl/ssl.h`. """ assert 0x2 == SESS_CACHE_SERVER def test_sess_cache_both(self): """ The value of `OpenSSL.SSL.SESS_CACHE_BOTH` 0x3, the value of `SSL_SESS_CACHE_BOTH` defined by `openssl/ssl.h`. """ assert 0x3 == SESS_CACHE_BOTH def test_sess_cache_no_auto_clear(self): """ The value of `OpenSSL.SSL.SESS_CACHE_NO_AUTO_CLEAR` 0x80, the value of `SSL_SESS_CACHE_NO_AUTO_CLEAR` defined by `openssl/ssl.h`. """ assert 0x80 == SESS_CACHE_NO_AUTO_CLEAR def test_sess_cache_no_internal_lookup(self): """ The value of `OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_LOOKUP` 0x100, the value of `SSL_SESS_CACHE_NO_INTERNAL_LOOKUP` defined by `openssl/ssl.h`. """ assert 0x100 == SESS_CACHE_NO_INTERNAL_LOOKUP def test_sess_cache_no_internal_store(self): """ The value of `OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_STORE` 0x200, the value of `SSL_SESS_CACHE_NO_INTERNAL_STORE` defined by `openssl/ssl.h`. """ assert 0x200 == SESS_CACHE_NO_INTERNAL_STORE def test_sess_cache_no_internal(self): """ The value of `OpenSSL.SSL.SESS_CACHE_NO_INTERNAL` 0x300, the value of `SSL_SESS_CACHE_NO_INTERNAL` defined by `openssl/ssl.h`. """ assert 0x300 == SESS_CACHE_NO_INTERNAL class TestMemoryBIO: """ Tests for `OpenSSL.SSL.Connection` using a memory BIO. """ def _server(self, sock): """ Create a new server-side SSL `Connection` object wrapped around `sock`. """ # Create the server side Connection. This is mostly setup boilerplate # - use TLSv1, use a particular certificate, etc. server_ctx = Context(SSLv23_METHOD) server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE) server_ctx.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, verify_cb, ) server_store = server_ctx.get_cert_store() server_ctx.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem) ) server_ctx.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem) ) server_ctx.check_privatekey() server_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) # Here the Connection is actually created. If None is passed as the # 2nd parameter, it indicates a memory BIO should be created. server_conn = Connection(server_ctx, sock) server_conn.set_accept_state() return server_conn def _client(self, sock): """ Create a new client-side SSL `Connection` object wrapped around `sock`. """ # Now create the client side Connection. Similar boilerplate to the # above. client_ctx = Context(SSLv23_METHOD) client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE) client_ctx.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, verify_cb, ) client_store = client_ctx.get_cert_store() client_ctx.use_privatekey( load_privatekey(FILETYPE_PEM, client_key_pem) ) client_ctx.use_certificate( load_certificate(FILETYPE_PEM, client_cert_pem) ) client_ctx.check_privatekey() client_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) client_conn = Connection(client_ctx, sock) client_conn.set_connect_state() return client_conn def test_memory_connect(self): """ Two `Connection`s which use memory BIOs can be manually connected by reading from the output of each and writing those bytes to the input of the other and in this way establish a connection and exchange application-level bytes with each other. """ server_conn = self._server(None) client_conn = self._client(None) # There should be no key or nonces yet. assert server_conn.master_key() is None assert server_conn.client_random() is None assert server_conn.server_random() is None # First, the handshake needs to happen. We'll deliver bytes back and # forth between the client and server until neither of them feels like # speaking any more. assert interact_in_memory(client_conn, server_conn) is None # Now that the handshake is done, there should be a key and nonces. assert server_conn.master_key() is not None assert server_conn.client_random() is not None assert server_conn.server_random() is not None assert server_conn.client_random() == client_conn.client_random() assert server_conn.server_random() == client_conn.server_random() assert server_conn.client_random() != server_conn.server_random() assert client_conn.client_random() != client_conn.server_random() # Export key material for other uses. cekm = client_conn.export_keying_material(b"LABEL", 32) sekm = server_conn.export_keying_material(b"LABEL", 32) assert cekm is not None assert sekm is not None assert cekm == sekm assert len(sekm) == 32 # Export key material for other uses with additional context. cekmc = client_conn.export_keying_material(b"LABEL", 32, b"CONTEXT") sekmc = server_conn.export_keying_material(b"LABEL", 32, b"CONTEXT") assert cekmc is not None assert sekmc is not None assert cekmc == sekmc assert cekmc != cekm assert sekmc != sekm # Export with alternate label cekmt = client_conn.export_keying_material(b"test", 32, b"CONTEXT") sekmt = server_conn.export_keying_material(b"test", 32, b"CONTEXT") assert cekmc != cekmt assert sekmc != sekmt # Here are the bytes we'll try to send. important_message = b"One if by land, two if by sea." server_conn.write(important_message) assert interact_in_memory(client_conn, server_conn) == ( client_conn, important_message, ) client_conn.write(important_message[::-1]) assert interact_in_memory(client_conn, server_conn) == ( server_conn, important_message[::-1], ) def test_socket_connect(self): """ Just like `test_memory_connect` but with an actual socket. This is primarily to rule out the memory BIO code as the source of any problems encountered while passing data over a `Connection` (if this test fails, there must be a problem outside the memory BIO code, as no memory BIO is involved here). Even though this isn't a memory BIO test, it's convenient to have it here. """ server_conn, client_conn = loopback() important_message = b"Help me Obi Wan Kenobi, you're my only hope." client_conn.send(important_message) msg = server_conn.recv(1024) assert msg == important_message # Again in the other direction, just for fun. important_message = important_message[::-1] server_conn.send(important_message) msg = client_conn.recv(1024) assert msg == important_message def test_socket_overrides_memory(self): """ Test that `OpenSSL.SSL.bio_read` and `OpenSSL.SSL.bio_write` don't work on `OpenSSL.SSL.Connection`() that use sockets. """ context = Context(SSLv23_METHOD) client = socket_any_family() clientSSL = Connection(context, client) with pytest.raises(TypeError): clientSSL.bio_read(100) with pytest.raises(TypeError): clientSSL.bio_write(b"foo") with pytest.raises(TypeError): clientSSL.bio_shutdown() def test_outgoing_overflow(self): """ If more bytes than can be written to the memory BIO are passed to `Connection.send` at once, the number of bytes which were written is returned and that many bytes from the beginning of the input can be read from the other end of the connection. """ server = self._server(None) client = self._client(None) interact_in_memory(client, server) size = 2**15 sent = client.send(b"x" * size) # Sanity check. We're trying to test what happens when the entire # input can't be sent. If the entire input was sent, this test is # meaningless. assert sent < size receiver, received = interact_in_memory(client, server) assert receiver is server # We can rely on all of these bytes being received at once because # loopback passes 2 ** 16 to recv - more than 2 ** 15. assert len(received) == sent def test_shutdown(self): """ `Connection.bio_shutdown` signals the end of the data stream from which the `Connection` reads. """ server = self._server(None) server.bio_shutdown() with pytest.raises(Error) as err: server.recv(1024) # We don't want WantReadError or ZeroReturnError or anything - it's a # handshake failure. assert type(err.value) in [Error, SysCallError] def test_unexpected_EOF(self): """ If the connection is lost before an orderly SSL shutdown occurs, `OpenSSL.SSL.SysCallError` is raised with a message of "Unexpected EOF" (or WSAECONNRESET on Windows). """ server_conn, client_conn = loopback() client_conn.sock_shutdown(SHUT_RDWR) with pytest.raises(SysCallError) as err: server_conn.recv(1024) if platform == "win32": assert err.value.args == (10054, "WSAECONNRESET") else: assert err.value.args == (-1, "Unexpected EOF") def _check_client_ca_list(self, func): """ Verify the return value of the `get_client_ca_list` method for server and client connections. :param func: A function which will be called with the server context before the client and server are connected to each other. This function should specify a list of CAs for the server to send to the client and return that same list. The list will be used to verify that `get_client_ca_list` returns the proper value at various times. """ server = self._server(None) client = self._client(None) assert client.get_client_ca_list() == [] assert server.get_client_ca_list() == [] ctx = server.get_context() expected = func(ctx) assert client.get_client_ca_list() == [] assert server.get_client_ca_list() == expected interact_in_memory(client, server) assert client.get_client_ca_list() == expected assert server.get_client_ca_list() == expected def test_set_client_ca_list_errors(self): """ `Context.set_client_ca_list` raises a `TypeError` if called with a non-list or a list that contains objects other than X509Names. """ ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.set_client_ca_list("spam") with pytest.raises(TypeError): ctx.set_client_ca_list(["spam"]) def test_set_empty_ca_list(self): """ If passed an empty list, `Context.set_client_ca_list` configures the context to send no CA names to the client and, on both the server and client sides, `Connection.get_client_ca_list` returns an empty list after the connection is set up. """ def no_ca(ctx): ctx.set_client_ca_list([]) return [] self._check_client_ca_list(no_ca) def test_set_one_ca_list(self): """ If passed a list containing a single X509Name, `Context.set_client_ca_list` configures the context to send that CA name to the client and, on both the server and client sides, `Connection.get_client_ca_list` returns a list containing that X509Name after the connection is set up. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) cadesc = cacert.get_subject() def single_ca(ctx): ctx.set_client_ca_list([cadesc]) return [cadesc] self._check_client_ca_list(single_ca) def test_set_multiple_ca_list(self): """ If passed a list containing multiple X509Name objects, `Context.set_client_ca_list` configures the context to send those CA names to the client and, on both the server and client sides, `Connection.get_client_ca_list` returns a list containing those X509Names after the connection is set up. """ secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) sedesc = secert.get_subject() cldesc = clcert.get_subject() def multiple_ca(ctx): L = [sedesc, cldesc] ctx.set_client_ca_list(L) return L self._check_client_ca_list(multiple_ca) def test_reset_ca_list(self): """ If called multiple times, only the X509Names passed to the final call of `Context.set_client_ca_list` are used to configure the CA names sent to the client. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() cldesc = clcert.get_subject() def changed_ca(ctx): ctx.set_client_ca_list([sedesc, cldesc]) ctx.set_client_ca_list([cadesc]) return [cadesc] self._check_client_ca_list(changed_ca) def test_mutated_ca_list(self): """ If the list passed to `Context.set_client_ca_list` is mutated afterwards, this does not affect the list of CA names sent to the client. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def mutated_ca(ctx): L = [cadesc] ctx.set_client_ca_list([cadesc]) L.append(sedesc) return [cadesc] self._check_client_ca_list(mutated_ca) def test_add_client_ca_wrong_args(self): """ `Context.add_client_ca` raises `TypeError` if called with a non-X509 object. """ ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.add_client_ca("spam") def test_one_add_client_ca(self): """ A certificate's subject can be added as a CA to be sent to the client with `Context.add_client_ca`. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) cadesc = cacert.get_subject() def single_ca(ctx): ctx.add_client_ca(cacert) return [cadesc] self._check_client_ca_list(single_ca) def test_multiple_add_client_ca(self): """ Multiple CA names can be sent to the client by calling `Context.add_client_ca` with multiple X509 objects. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def multiple_ca(ctx): ctx.add_client_ca(cacert) ctx.add_client_ca(secert) return [cadesc, sedesc] self._check_client_ca_list(multiple_ca) def test_set_and_add_client_ca(self): """ A call to `Context.set_client_ca_list` followed by a call to `Context.add_client_ca` results in using the CA names from the first call and the CA name from the second call. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() cldesc = clcert.get_subject() def mixed_set_add_ca(ctx): ctx.set_client_ca_list([cadesc, sedesc]) ctx.add_client_ca(clcert) return [cadesc, sedesc, cldesc] self._check_client_ca_list(mixed_set_add_ca) def test_set_after_add_client_ca(self): """ A call to `Context.set_client_ca_list` after a call to `Context.add_client_ca` replaces the CA name specified by the former call with the names specified by the latter call. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def set_replaces_add_ca(ctx): ctx.add_client_ca(clcert) ctx.set_client_ca_list([cadesc]) ctx.add_client_ca(secert) return [cadesc, sedesc] self._check_client_ca_list(set_replaces_add_ca) class TestInfoConstants: """ Tests for assorted constants exposed for use in info callbacks. """ def test_integers(self): """ All of the info constants are integers. This is a very weak test. It would be nice to have one that actually verifies that as certain info events happen, the value passed to the info callback matches up with the constant exposed by OpenSSL.SSL. """ for const in [ SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT, SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP, SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT, SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE, ]: assert isinstance(const, int) # These constants don't exist on OpenSSL 1.1.0 for const in [ SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE, ]: assert const is None or isinstance(const, int) class TestRequires: """ Tests for the decorator factory used to conditionally raise NotImplementedError when older OpenSSLs are used. """ def test_available(self): """ When the OpenSSL functionality is available the decorated functions work appropriately. """ feature_guard = _make_requires(True, "Error text") results = [] @feature_guard def inner(): results.append(True) return True assert inner() is True assert [True] == results def test_unavailable(self): """ When the OpenSSL functionality is not available the decorated function does not execute and NotImplementedError is raised. """ feature_guard = _make_requires(False, "Error text") @feature_guard def inner(): # pragma: nocover pytest.fail("Should not be called") with pytest.raises(NotImplementedError) as e: inner() assert "Error text" in str(e.value) class TestOCSP: """ Tests for PyOpenSSL's OCSP stapling support. """ sample_ocsp_data = b"this is totally ocsp data" def _client_connection(self, callback, data, request_ocsp=True): """ Builds a client connection suitable for using OCSP. :param callback: The callback to register for OCSP. :param data: The opaque data object that will be handed to the OCSP callback. :param request_ocsp: Whether the client will actually ask for OCSP stapling. Useful for testing only. """ ctx = Context(SSLv23_METHOD) ctx.set_ocsp_client_callback(callback, data) client = Connection(ctx) if request_ocsp: client.request_ocsp() client.set_connect_state() return client def _server_connection(self, callback, data): """ Builds a server connection suitable for using OCSP. :param callback: The callback to register for OCSP. :param data: The opaque data object that will be handed to the OCSP callback. """ ctx = Context(SSLv23_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) ctx.set_ocsp_server_callback(callback, data) server = Connection(ctx) server.set_accept_state() return server def test_callbacks_arent_called_by_default(self): """ If both the client and the server have registered OCSP callbacks, but the client does not send the OCSP request, neither callback gets called. """ def ocsp_callback(*args, **kwargs): # pragma: nocover pytest.fail("Should not be called") client = self._client_connection( callback=ocsp_callback, data=None, request_ocsp=False ) server = self._server_connection(callback=ocsp_callback, data=None) handshake_in_memory(client, server) def test_client_negotiates_without_server(self): """ If the client wants to do OCSP but the server does not, the handshake succeeds, and the client callback fires with an empty byte string. """ called = [] def ocsp_callback(conn, ocsp_data, ignored): called.append(ocsp_data) return True client = self._client_connection(callback=ocsp_callback, data=None) server = loopback_server_factory(socket=None) handshake_in_memory(client, server) assert len(called) == 1 assert called[0] == b"" def test_client_receives_servers_data(self): """ The data the server sends in its callback is received by the client. """ calls = [] def server_callback(*args, **kwargs): return self.sample_ocsp_data def client_callback(conn, ocsp_data, ignored): calls.append(ocsp_data) return True client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) handshake_in_memory(client, server) assert len(calls) == 1 assert calls[0] == self.sample_ocsp_data def test_callbacks_are_invoked_with_connections(self): """ The first arguments to both callbacks are their respective connections. """ client_calls = [] server_calls = [] def client_callback(conn, *args, **kwargs): client_calls.append(conn) return True def server_callback(conn, *args, **kwargs): server_calls.append(conn) return self.sample_ocsp_data client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) handshake_in_memory(client, server) assert len(client_calls) == 1 assert len(server_calls) == 1 assert client_calls[0] is client assert server_calls[0] is server def test_opaque_data_is_passed_through(self): """ Both callbacks receive an opaque, user-provided piece of data in their callbacks as the final argument. """ calls = [] def server_callback(*args): calls.append(args) return self.sample_ocsp_data def client_callback(*args): calls.append(args) return True sentinel = object() client = self._client_connection( callback=client_callback, data=sentinel ) server = self._server_connection( callback=server_callback, data=sentinel ) handshake_in_memory(client, server) assert len(calls) == 2 assert calls[0][-1] is sentinel assert calls[1][-1] is sentinel def test_server_returns_empty_string(self): """ If the server returns an empty bytestring from its callback, the client callback is called with the empty bytestring. """ client_calls = [] def server_callback(*args): return b"" def client_callback(conn, ocsp_data, ignored): client_calls.append(ocsp_data) return True client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) handshake_in_memory(client, server) assert len(client_calls) == 1 assert client_calls[0] == b"" def test_client_returns_false_terminates_handshake(self): """ If the client returns False from its callback, the handshake fails. """ def server_callback(*args): return self.sample_ocsp_data def client_callback(*args): return False client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) with pytest.raises(Error): handshake_in_memory(client, server) def test_exceptions_in_client_bubble_up(self): """ The callbacks thrown in the client callback bubble up to the caller. """ class SentinelException(Exception): pass def server_callback(*args): return self.sample_ocsp_data def client_callback(*args): raise SentinelException() client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) with pytest.raises(SentinelException): handshake_in_memory(client, server) def test_exceptions_in_server_bubble_up(self): """ The callbacks thrown in the server callback bubble up to the caller. """ class SentinelException(Exception): pass def server_callback(*args): raise SentinelException() def client_callback(*args): # pragma: nocover pytest.fail("Should not be called") client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) with pytest.raises(SentinelException): handshake_in_memory(client, server) def test_server_must_return_bytes(self): """ The server callback must return a bytestring, or a TypeError is thrown. """ def server_callback(*args): return self.sample_ocsp_data.decode("ascii") def client_callback(*args): # pragma: nocover pytest.fail("Should not be called") client = self._client_connection(callback=client_callback, data=None) server = self._server_connection(callback=server_callback, data=None) with pytest.raises(TypeError): handshake_in_memory(client, server) class TestDTLS: # The way you would expect DTLSv1_listen to work is: # # - it reads packets in a loop # - when it finds a valid ClientHello, it returns # - now the handshake can proceed # # However, on older versions of OpenSSL, it did something "cleverer". The # way it worked is: # # - it "peeks" into the BIO to see the next packet without consuming it # - if *not* a valid ClientHello, then it reads the packet to consume it # and loops around # - if it *is* a valid ClientHello, it *leaves the packet in the BIO*, and # returns # - then the handshake finds the ClientHello in the BIO and reads it a # second time. # # I'm not sure exactly when this switched over. The OpenSSL v1.1.1 in # Ubuntu 18.04 has the old behavior. The OpenSSL v1.1.1 in Ubuntu 20.04 has # the new behavior. There doesn't seem to be any mention of this change in # the OpenSSL v1.1.1 changelog, but presumably it changed in some point # release or another. Presumably in 2025 or so there will be only new # OpenSSLs around we can delete this whole comment and the weird # workaround. If anyone is still using this library by then, which seems # both depressing and inevitable. # # Anyway, why do we care? The reason is that the old strategy has a # problem: the "peek" operation is only defined on "DGRAM BIOs", which are # a special type of object that is different from the more familiar "socket # BIOs" and "memory BIOs". If you *don't* have a DGRAM BIO, and you try to # peek into the BIO... then it silently degrades to a full-fledged "read" # operation that consumes the packet. Which is a problem if your algorithm # depends on leaving the packet in the BIO to be read again later. # # So on old OpenSSL, we have a problem: # # - we can't use a DGRAM BIO, because cryptography/pyopenssl don't wrap the # relevant APIs, nor should they. # # - if we use a socket BIO, then the first time DTLSv1_listen sees an # invalid packet (like for example... the challenge packet that *every # DTLS handshake starts with before the real ClientHello!*), it tries to # first "peek" it, and then "read" it. But since the first "peek" # consumes the packet, the second "read" ends up hanging or consuming # some unrelated packet, which is undesirable. So you can't even get to # the handshake stage successfully. # # - if we use a memory BIO, then DTLSv1_listen works OK on invalid packets # -- first the "peek" consumes them, and then it tries to "read" again to # consume them, which fails immediately, and OpenSSL ignores the failure. # So it works by accident. BUT, when we get a valid ClientHello, we have # a problem: DTLSv1_listen tries to "peek" it and then leave it in the # read BIO for do_handshake to consume. But instead "peek" consumes the # packet, so it's not there where do_handshake is expecting it, and the # handshake fails. # # Fortunately (if that's the word), we can work around the memory BIO # problem. (Which is good, because in real life probably all our users will # be using memory BIOs.) All we have to do is to save the valid ClientHello # before calling DTLSv1_listen, and then after it returns we push *a second # copy of it* of the packet memory BIO before calling do_handshake. This # fakes out OpenSSL and makes it think the "peek" operation worked # correctly, and we can go on with our lives. # # In fact, we push the second copy of the ClientHello unconditionally. On # new versions of OpenSSL, this is unnecessary, but harmless, because the # DTLS state machine treats it like a network hiccup that duplicated a # packet, which DTLS is robust against. # Arbitrary number larger than any conceivable handshake volley. LARGE_BUFFER = 65536 def _test_handshake_and_data(self, srtp_profile): s_ctx = Context(DTLS_METHOD) def generate_cookie(ssl): return b"xyzzy" def verify_cookie(ssl, cookie): return cookie == b"xyzzy" s_ctx.set_cookie_generate_callback(generate_cookie) s_ctx.set_cookie_verify_callback(verify_cookie) s_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) s_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) s_ctx.set_options(OP_NO_QUERY_MTU) if srtp_profile is not None: s_ctx.set_tlsext_use_srtp(srtp_profile) s = Connection(s_ctx) s.set_accept_state() c_ctx = Context(DTLS_METHOD) c_ctx.set_options(OP_NO_QUERY_MTU) if srtp_profile is not None: c_ctx.set_tlsext_use_srtp(srtp_profile) c = Connection(c_ctx) c.set_connect_state() # These are mandatory, because openssl can't guess the MTU for a memory # bio and will produce a mysterious error if you make it try. c.set_ciphertext_mtu(1500) s.set_ciphertext_mtu(1500) latest_client_hello = None def pump_membio(label, source, sink): try: chunk = source.bio_read(self.LARGE_BUFFER) except WantReadError: return False # I'm not sure this check is needed, but I'm not sure it's *not* # needed either: if not chunk: # pragma: no cover return False # Gross hack: if this is a ClientHello, save it so we can find it # later. See giant comment above. try: # if ContentType == handshake and HandshakeType == # client_hello: if chunk[0] == 22 and chunk[13] == 1: nonlocal latest_client_hello latest_client_hello = chunk except IndexError: # pragma: no cover pass print(f"{label}: {chunk.hex()}") sink.bio_write(chunk) return True def pump(): # Raises if there was no data to pump, to avoid infinite loops if # we aren't making progress. assert pump_membio("s -> c", s, c) or pump_membio("c -> s", c, s) c_handshaking = True s_listening = True s_handshaking = False first = True while c_handshaking or s_listening or s_handshaking: if not first: pump() first = False if c_handshaking: try: c.do_handshake() except WantReadError: pass else: c_handshaking = False if s_listening: try: s.DTLSv1_listen() except WantReadError: pass else: s_listening = False s_handshaking = True # Write the duplicate ClientHello. See giant comment above. s.bio_write(latest_client_hello) if s_handshaking: try: s.do_handshake() except WantReadError: pass else: s_handshaking = False s.write(b"hello") pump() assert c.read(100) == b"hello" c.write(b"goodbye") pump() assert s.read(100) == b"goodbye" # Check whether SRTP was negotiated if srtp_profile is not None: assert s.get_selected_srtp_profile() == srtp_profile assert c.get_selected_srtp_profile() == srtp_profile else: assert s.get_selected_srtp_profile() == b"" assert c.get_selected_srtp_profile() == b"" # Check that the MTU set/query functions are doing *something* c.set_ciphertext_mtu(1000) try: assert 500 < c.get_cleartext_mtu() < 1000 except NotImplementedError: # OpenSSL 1.1.0 and earlier pass c.set_ciphertext_mtu(500) try: assert 0 < c.get_cleartext_mtu() < 500 except NotImplementedError: # OpenSSL 1.1.0 and earlier pass def test_it_works_at_all(self): self._test_handshake_and_data(srtp_profile=None) def test_it_works_with_srtp(self): self._test_handshake_and_data(srtp_profile=b"SRTP_AES128_CM_SHA1_80") def test_timeout(self, monkeypatch): c_ctx = Context(DTLS_METHOD) c = Connection(c_ctx) # No timeout before the handshake starts. assert c.DTLSv1_get_timeout() is None assert c.DTLSv1_handle_timeout() is False # Start handshake and check there is data to send. c.set_connect_state() try: c.do_handshake() except SSL.WantReadError: pass assert c.bio_read(self.LARGE_BUFFER) # There should now be an active timeout. seconds = c.DTLSv1_get_timeout() assert seconds is not None # Handle the timeout and check there is data to send. time.sleep(seconds) assert c.DTLSv1_handle_timeout() is True assert c.bio_read(self.LARGE_BUFFER) # After the maximum number of allowed timeouts is reached, # DTLSv1_handle_timeout will return -1. # # Testing this directly is prohibitively time consuming as the timeout # duration is doubled on each retry, so the best we can do is to mock # this condition. monkeypatch.setattr(_lib, "DTLSv1_handle_timeout", lambda x: -1) with pytest.raises(Error): c.DTLSv1_handle_timeout() pyopenssl-24.2.1/tests/test_util.py000066400000000000000000000011421465571700000173670ustar00rootroot00000000000000import pytest from OpenSSL._util import exception_from_error_queue, lib class TestErrors: """ Tests for handling of certain OpenSSL error cases. """ def test_exception_from_error_queue_nonexistent_reason(self): """ :func:`exception_from_error_queue` raises ``ValueError`` when it encounters an OpenSSL error code which does not have a reason string. """ lib.ERR_put_error(lib.ERR_LIB_EVP, 0, 1112, b"", 10) with pytest.raises(ValueError) as exc: exception_from_error_queue(ValueError) assert exc.value.args[0][0][2] == "" pyopenssl-24.2.1/tests/util.py000066400000000000000000000111731465571700000163350ustar00rootroot00000000000000# Copyright (C) Jean-Paul Calderone # Copyright (C) Twisted Matrix Laboratories. # See LICENSE for details. """ Helpers for the OpenSSL test suite, largely copied from U{Twisted}. """ # This is the UTF-8 encoding of the SNOWMAN unicode code point. NON_ASCII = b"\xe2\x98\x83".decode("utf-8") def is_consistent_type(theType, name, *constructionArgs): """ Perform various assertions about *theType* to ensure that it is a well-defined type. This is useful for extension types, where it's pretty easy to do something wacky. If something about the type is unusual, an exception will be raised. :param theType: The type object about which to make assertions. :param name: A string giving the name of the type. :param constructionArgs: Positional arguments to use with *theType* to create an instance of it. """ assert theType.__name__ == name assert isinstance(theType, type) instance = theType(*constructionArgs) assert type(instance) is theType return True class EqualityTestsMixin: """ A mixin defining tests for the standard implementation of C{==} and C{!=}. """ def anInstance(self): """ Return an instance of the class under test. Each call to this method must return a different object. All objects returned must be equal to each other. """ raise NotImplementedError() def anotherInstance(self): """ Return an instance of the class under test. Each call to this method must return a different object. The objects must not be equal to the objects returned by C{anInstance}. They may or may not be equal to each other (they will not be compared against each other). """ raise NotImplementedError() def test_identicalEq(self): """ An object compares equal to itself using the C{==} operator. """ o = self.anInstance() assert o == o def test_identicalNe(self): """ An object doesn't compare not equal to itself using the C{!=} operator. """ o = self.anInstance() assert not (o != o) def test_sameEq(self): """ Two objects that are equal to each other compare equal to each other using the C{==} operator. """ a = self.anInstance() b = self.anInstance() assert a == b def test_sameNe(self): """ Two objects that are equal to each other do not compare not equal to each other using the C{!=} operator. """ a = self.anInstance() b = self.anInstance() assert not (a != b) def test_differentEq(self): """ Two objects that are not equal to each other do not compare equal to each other using the C{==} operator. """ a = self.anInstance() b = self.anotherInstance() assert not (a == b) def test_differentNe(self): """ Two objects that are not equal to each other compare not equal to each other using the C{!=} operator. """ a = self.anInstance() b = self.anotherInstance() assert a != b def test_anotherTypeEq(self): """ The object does not compare equal to an object of an unrelated type (which does not implement the comparison) using the C{==} operator. """ a = self.anInstance() b = object() assert not (a == b) def test_anotherTypeNe(self): """ The object compares not equal to an object of an unrelated type (which does not implement the comparison) using the C{!=} operator. """ a = self.anInstance() b = object() assert a != b def test_delegatedEq(self): """ The result of comparison using C{==} is delegated to the right-hand operand if it is of an unrelated type. """ class Delegate: def __eq__(self, other): # Do something crazy and obvious. return [self] a = self.anInstance() b = Delegate() assert (a == b) == [b] def test_delegateNe(self): """ The result of comparison using C{!=} is delegated to the right-hand operand if it is of an unrelated type. """ class Delegate: def __ne__(self, other): # Do something crazy and obvious. return [self] a = self.anInstance() b = Delegate() assert (a != b) == [b] # The type name expected in warnings about using the wrong string type. WARNING_TYPE_EXPECTED = "str" pyopenssl-24.2.1/tox.ini000066400000000000000000000031521465571700000151550ustar00rootroot00000000000000[tox] envlist = py{py3,37,38,39,310,311,312}{,-cryptographyMinimum}{,-useWheel}{,-randomorder},py311-twistedTrunk,check-manifest,lint,py311-mypy,docs,coverage-report [testenv] allowlist_externals = openssl passenv = ARCHFLAGS CFLAGS LC_ALL LDFLAGS PATH LD_LIBRARY_PATH TERM RUSTUP_TOOLCHAIN RUSTUP_HOME extras = test deps = coverage>=4.2 cryptographyMinimum: cryptography==41.0.5 randomorder: pytest-randomly setenv = # Do not allow the executing environment to pollute the test environment # with extra packages. PYTHONPATH= PIP_NO_BINARY=cryptography useWheel: PIP_NO_BINARY= commands = cryptographyMain: pip install -U git+https://github.com/pyca/cryptography.git openssl version coverage run --parallel -m OpenSSL.debug coverage run --parallel -m pytest -v {posargs} [testenv:py311-twistedTrunk] deps = pyasn1!=0.5.0 Twisted[all_non_platform] @ git+https://github.com/twisted/twisted setenv = commands = python -m OpenSSL.debug python -m twisted.trial -j4 --reporter=text twisted [testenv:lint] basepython = python3 deps = ruff skip_install = true commands = ruff check . ruff format --check . [testenv:py311-mypy] deps = mypy==1.1.1 skip_install = true commands = mypy src [testenv:check-manifest] deps = check-manifest skip_install = true commands = check-manifest [testenv:docs] extras = docs commands = sphinx-build -W -b html doc doc/_build/html {posargs} [testenv:coverage-report] deps = coverage[toml]>=4.2 skip_install = true commands = coverage combine coverage report