diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:01:42 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-04-08 16:01:42 +0000 |
commit | 38a6d13e19040896f58f0a1afa51840d7474a719 (patch) | |
tree | edb38e1ed760de3031979ef6dcd7a6a77d64c21b | |
parent | dcf0c73ae5177ddda1411c5e713d81194c756bee (diff) | |
parent | 751caf63d05da8477d934da5c05316ddeb4f64de (diff) | |
download | pyopenssl-aml_tz2_305400300.tar.gz |
Snap for 8426163 from 751caf63d05da8477d934da5c05316ddeb4f64de to mainline-tzdata2-releaseandroid-mainline-12.0.0_r112aml_tz2_305400500aml_tz2_305400300aml_tz2_305400100aml_tz2_304500300aml_tz2_303900110aml_tz2_303900102aml_tz2_303800002aml_tz2_303800001aml_tz2_303200001android12-mainline-tzdata2-releaseaml_tz2_305400100
Change-Id: I0eb792c7d007b313d47e23bc4c14391c37a2b49b
61 files changed, 3690 insertions, 3224 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 1230149..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index b65b728..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: CI -on: - pull_request: {} - push: {} - -jobs: - linux: - runs-on: ubuntu-latest - strategy: - matrix: - PYTHON: - # Base builds - - {VERSION: "2.7", TOXENV: "py27"} - - {VERSION: "3.5", TOXENV: "py35"} - - {VERSION: "3.6", TOXENV: "py36"} - - {VERSION: "3.7", TOXENV: "py37"} - - {VERSION: "3.8", TOXENV: "py38"} - - {VERSION: "3.9", TOXENV: "py39"} - - {VERSION: "pypy2", TOXENV: "pypy"} - - {VERSION: "pypy3", TOXENV: "pypy3"} - # -cryptographyMaster - - {VERSION: "3.6", TOXENV: "py36-cryptographyMaster"} - - {VERSION: "3.7", TOXENV: "py37-cryptographyMaster"} - - {VERSION: "3.8", TOXENV: "py38-cryptographyMaster"} - - {VERSION: "3.9", TOXENV: "py39-cryptographyMaster"} - - {VERSION: "pypy3", TOXENV: "pypy3-cryptographyMaster"} - # -cryptographyMinimum - - {VERSION: "2.7", TOXENV: "py27-cryptographyMinimum"} - - {VERSION: "3.5", TOXENV: "py35-cryptographyMinimum"} - - {VERSION: "3.6", TOXENV: "py36-cryptographyMinimum"} - - {VERSION: "3.7", TOXENV: "py37-cryptographyMinimum"} - - {VERSION: "3.8", TOXENV: "py38-cryptographyMinimum"} - - {VERSION: "3.9", TOXENV: "py39-cryptographyMinimum"} - - {VERSION: "pypy2", TOXENV: "pypy-cryptographyMinimum"} - - {VERSION: "pypy3", TOXENV: "pypy3-cryptographyMinimum"} - # Random order - - {VERSION: "2.7", TOXENV: "py27-randomorder"} - - {VERSION: "3.9", TOXENV: "py39-randomorder"} - # Downstreams - - {VERSION: "3.7", TOXENV: "py37-twistedMaster"} - # Meta - - {VERSION: "2.7", TOXENV: "check-manifest"} - - {VERSION: "2.7", TOXENV: "pypi-readme"} - - {VERSION: "3.9", TOXENV: "flake8"} - - {VERSION: "2.7", TOXENV: "docs"} - name: "${{ matrix.PYTHON.TOXENV }}" - steps: - - uses: actions/checkout@v2 - - name: Setup python - uses: actions/setup-python@v2 - 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: - - {CONTAINER: "stretch", TOXENV: "py27"} - - {CONTAINER: "stretch", TOXENV: "py35"} - name: "${{ matrix.TEST.TOXENV }} on ${{ matrix.TEST.CONTAINER }}" - steps: - - uses: actions/checkout@v2 - - run: tox -v - env: - TOXENV: ${{ matrix.TEST.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.TEST.TOXENV }} on ${{ matrix.TEST.CONTAINER }}" diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml deleted file mode 100644 index 7356bd4..0000000 --- a/.github/workflows/lock.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Lock Issues -on: - schedule: - - cron: '0 0 * * *' - -jobs: - lock: - runs-on: ubuntu-latest - steps: - - uses: dessant/lock-threads@v2 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - issue-lock-inactive-days: 90 - pr-lock-inactive-days: 90 @@ -11,4 +11,3 @@ doc/_build/ examples/simple/*.cert examples/simple/*.pkey .cache -.mypy_cache
\ No newline at end of file diff --git a/.mention-bot b/.mention-bot new file mode 100644 index 0000000..9a20eb3 --- /dev/null +++ b/.mention-bot @@ -0,0 +1,3 @@ +{ + "userBlacklist": ["exarkun"] +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..52131e8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,148 @@ +dist: trusty +sudo: false +language: python + +cache: + directories: + - $HOME/.cache/pip + +env: + global: + - LC_ALL=en_US.UTF-8 + +matrix: + include: + - language: generic + os: osx + osx_image: xcode10.1 + env: TOXENV=py27 + - language: generic + os: osx + osx_image: xcode10.1 + env: TOXENV=py27 OPENSSL=1.1.0 + - python: "2.7" # these are just to make travis's UI a bit prettier + env: TOXENV=py27 + - python: "3.4" + env: TOXENV=py34 + - python: "3.5" + env: TOXENV=py35 + - python: "3.6" + env: TOXENV=py36 + - python: "3.7" + env: TOXENV=py37 + dist: xenial + sudo: true + - python: "pypy" + env: TOXENV=pypy + - python: "pypy3" + env: TOXENV=pypy3 + + # Also run the tests against cryptography master. + - python: "2.7" + env: TOXENV=py27-cryptographyMaster + - python: "3.4" + env: TOXENV=py34-cryptographyMaster + - python: "3.5" + env: TOXENV=py35-cryptographyMaster + - python: "3.6" + env: TOXENV=py36-cryptographyMaster + - python: "3.7" + env: TOXENV=py37-cryptographyMaster + dist: xenial + sudo: true + - python: "pypy" + env: TOXENV=pypy-cryptographyMaster + - python: "pypy3" + env: TOXENV=pypy3-cryptographyMaster + + # And current minimum cryptography version. + - python: "2.7" + env: TOXENV=py27-cryptographyMinimum + - python: "3.4" + env: TOXENV=py34-cryptographyMinimum + - python: "3.5" + env: TOXENV=py35-cryptographyMinimum + - python: "3.6" + env: TOXENV=py36-cryptographyMinimum + - python: "3.7" + env: TOXENV=py37-cryptographyMinimum + dist: xenial + sudo: true + - python: "pypy" + env: TOXENV=pypy-cryptographyMinimum + - python: "pypy3" + env: TOXENV=pypy3-cryptographyMinimum + + + # Make sure we don't break Twisted or urllib3 + - python: "2.7" + env: TOXENV=py27-twistedMaster + - python: "3.5" + env: TOXENV=py35-urllib3Master + + + # Meta + - python: "2.7" + env: TOXENV=check-manifest + + - python: "2.7" + env: TOXENV=pypi-readme + + - python: "2.7" + env: TOXENV=flake8 + + - python: "2.7" + env: TOXENV=docs + + # Let the cryptography master builds fail because they might be caused by + # cryptography changes that are beyond our control. + allow_failures: + - env: TOXENV=py27-cryptographyMaster + - env: TOXENV=py34-cryptographyMaster + - env: TOXENV=py35-cryptographyMaster + - env: TOXENV=py36-cryptographyMaster + - env: TOXENV=py37-cryptographyMaster + - env: TOXENV=pypy-cryptographyMaster + - env: TOXENV=pypy3-cryptographyMaster + + +install: + - | + if [[ "$(uname -s)" == 'Darwin' ]]; then + brew update + if [[ "${OPENSSL}" == "1.1.0" ]]; then + brew upgrade openssl@1.1 + else + brew upgrade openssl + fi + curl -O https://bootstrap.pypa.io/get-pip.py + python get-pip.py --user + python -m pip install --user virtualenv + else + pip install virtualenv + fi + python -m virtualenv ~/.venv + ~/.venv/bin/pip install tox coverage + +script: + - | + if [[ "$(uname -s)" == 'Darwin' ]]; then + # set our flags to use homebrew openssl + if [[ "${OPENSSL}" == "1.1.0" ]]; then + export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib" + export CFLAGS="-I/usr/local/opt/openssl@1.1/include" + export PATH="/usr/local/opt/openssl@1.1/bin:$PATH" + else + export LDFLAGS="-L/usr/local/opt/openssl/lib" + export CFLAGS="-I/usr/local/opt/openssl/include" + export PATH="/usr/local/opt/openssl/bin:$PATH" + fi + fi + openssl version + ~/.venv/bin/tox -v + +after_script: + - ./.travis/upload_coverage.sh + +notifications: + email: false diff --git a/.travis/install_urllib3.sh b/.travis/install_urllib3.sh new file mode 100755 index 0000000..1324ded --- /dev/null +++ b/.travis/install_urllib3.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e +set -x + +git clone --depth 1 https://github.com/shazow/urllib3.git +pip install -r ./urllib3/dev-requirements.txt +pip install ./urllib3[socks] diff --git a/.travis/upload_coverage.sh b/.travis/upload_coverage.sh new file mode 100755 index 0000000..02ea5f4 --- /dev/null +++ b/.travis/upload_coverage.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e +set -x + +NO_COVERAGE_TOXENVS=(pypy docs check-manifest pypi-readme flake8) +if ! [[ "${NO_COVERAGE_TOXENVS[*]}" =~ "${TOXENV}" ]]; then + source ~/.venv/bin/activate + coverage combine + bash <(curl -s https://codecov.io/bash) -e TRAVIS_OS_NAME,TOXENV,OPENSSL +fi diff --git a/Android.bp b/Android.bp deleted file mode 100644 index dd1c1f9..0000000 --- a/Android.bp +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (C) 2021 The Android Open Source Project -// -// 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. - -package { - default_applicable_licenses: ["external_python_pyopenssl_license"], -} - -// Added automatically by a large-scale-change -// See: http://go/android-license-faq -license { - name: "external_python_pyopenssl_license", - visibility: [":__subpackages__"], - license_kinds: [ - "SPDX-license-identifier-Apache-2.0", - ], - license_text: [ - "LICENSE", - ], -} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 63e74cb..2dda0d2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,92 +4,6 @@ Changelog Versions are year-based with a strict backward-compatibility policy. The third digit is only for regressions. -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.loads_pkcs7`` and ``OpenSSL.crypto.loads_pkcs12``. - -Changes: -^^^^^^^^ - -- Added a new optional ``chain`` parameter to ``OpenSSL.crypto.X509StoreContext()`` - where additional untrusted certificates can be specified to help chain building. - `#948 <https://github.com/pyca/pyopenssl/pull/948>`_ -- Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted - certificate file bundles and/or directories for verification. - `#943 <https://github.com/pyca/pyopenssl/pull/943>`_ -- Added ``Context.set_keylog_callback`` to log key material. - `#910 <https://github.com/pyca/pyopenssl/pull/910>`_ -- Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the - verified certificate chain of the peer. - `#894 <https://github.com/pyca/pyopenssl/pull/894>`_. -- Make verification callback optional in ``Context.set_verify``. - If omitted, OpenSSL's default verification is used. - `#933 <https://github.com/pyca/pyopenssl/pull/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 <https://github.com/pyca/pyopenssl/pull/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 <https://github.com/pyca/pyopenssl/pull/814>`_ -- The minimum ``cryptography`` version is now 2.8 due to issues on macOS with a transitive dependency. - `#875 <https://github.com/pyca/pyopenssl/pull/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 <https://github.com/pyca/pyopenssl/pull/820>`_ - - -Changes: -^^^^^^^^ - -- Support ``bytearray`` in ``SSL.Connection.send()`` by using cffi's from_buffer. - `#852 <https://github.com/pyca/pyopenssl/pull/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) ------------------- diff --git a/INSTALL.rst b/INSTALL.rst index 75540d5..71fe1ed 100644 --- a/INSTALL.rst +++ b/INSTALL.rst @@ -25,9 +25,14 @@ Supported OpenSSL Versions pyOpenSSL supports the same platforms and releases as the upstream cryptography project `does <https://cryptography.io/en/latest/installation/#supported-platforms>`_. Currently that means: +- 1.0.1 - 1.0.2 - 1.1.0 -- 1.1.1 + +If you need support for older releases, the following pinned versions will work: + +- **OpenSSL 0.9.8**: ``'pyOpenSSL<17.0' 'cryptography<1.4'`` +- **OpenSSL 1.0.0**: ``'pyOpenSSL<17.1' 'cryptography<1.7'`` You can always find out the versions of pyOpenSSL, cryptography, and the linked OpenSSL by running ``python -m OpenSSL.debug``. diff --git a/MANIFEST.in b/MANIFEST.in index 65b7dc3..036bcdb 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,11 @@ include LICENSE MANIFEST.in *.rst tox.ini .coveragerc -exclude codecov.yml +exclude leakcheck codecov.yml recursive-include tests *.py recursive-include doc * +recursive-include examples * +recursive-include rpm * +recursive-exclude leakcheck *.py *.pem +recursive-exclude examples/simple *.cert *.pkey prune doc/_build prune .travis +prune .mention-bot @@ -9,11 +9,11 @@ third_party { type: GIT value: "https://github.com/pyca/pyopenssl" } - version: "20.0.1" + version: "19.0.0" license_type: NOTICE last_upgrade_date { - year: 2020 - month: 12 - day: 15 + year: 2019 + month: 5 + day: 13 } } @@ -0,0 +1 @@ +LICENSE
\ No newline at end of file @@ -6,8 +6,8 @@ pyOpenSSL -- A Python wrapper around the OpenSSL library :target: https://pyopenssl.org/en/stable/ :alt: Stable Docs -.. image:: https://travis-ci.com/pyca/pyopenssl.svg?branch=master - :target: https://travis-ci.com/pyca/pyopenssl +.. image:: https://travis-ci.org/pyca/pyopenssl.svg?branch=master + :target: https://travis-ci.org/pyca/pyopenssl :alt: Build status .. image:: https://codecov.io/github/pyca/pyopenssl/branch/master/graph/badge.svg diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000..61a80b2 --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,8 @@ +{ + "presubmit" : [ + { + "name" : "acloud_test", + "host" : true + } + ] +} diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst index ead1452..c678b28 100644 --- a/doc/api/ssl.rst +++ b/doc/api/ssl.rst @@ -74,7 +74,6 @@ Context, Connection. 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. @@ -119,15 +118,6 @@ Context, Connection. 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:: SSLeay_version diff --git a/doc/conf.py b/doc/conf.py index dd8d011..3940dd2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -3,7 +3,7 @@ # 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. +# This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,6 +11,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import datetime import codecs import os import re @@ -31,9 +32,8 @@ def read_file(*parts): def find_version(*file_paths): version_file = read_file(*file_paths) - version_match = re.search( - r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M - ) + 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.") @@ -45,34 +45,34 @@ 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('.')) +#sys.path.insert(0, os.path.abspath('.')) -# -- General configuration ---------------------------------------------------- +# -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "1.0" +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", + 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +templates_path = ['_templates'] # The suffix of source filenames. -source_suffix = ".rst" +source_suffix = '.rst' # The encoding of source files. -# source_encoding = 'utf-8-sig' +#source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = "index" +master_doc = 'index' # General information about the project. -project = u"pyOpenSSL" +project = u'pyOpenSSL' authors = u"The pyOpenSSL developers" copyright = u"2001 " + authors @@ -87,74 +87,73 @@ release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None +#language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -# today = '' +#today = '' # Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +#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"] +exclude_patterns = ['_build'] -# The reST default role (used for this markup `text`) to use for all documents. -# default_role = None +# 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 +#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 +#add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -# show_authors = False +#show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" +pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +#modindex_common_prefix = [] -# -- Options for HTML output -------------------------------------------------- +# -- 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" +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 = '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 = {} +#html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +#html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # "<project> v<release> documentation". -# html_title = None +#html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +#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 +#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 +#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, @@ -163,92 +162,96 @@ if not on_rtd: # only import and set the theme if we're building docs locally # 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' +#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 +#html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -# html_sidebars = {} +#html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = {} +#html_additional_pages = {} # If false, no module index is generated. -# html_domain_indices = True +#html_domain_indices = True # If false, no index is generated. -# html_use_index = True +#html_use_index = True # If true, the index is split into individual pages for each letter. -# html_split_index = False +#html_split_index = False # If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True +#html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True +#html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True +#html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a <link> tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -# html_use_opensearch = '' +#html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None +#html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = "pyOpenSSLdoc" +htmlhelp_basename = 'pyOpenSSLdoc' -# -- Options for LaTeX output ------------------------------------------------- +# -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). -# latex_paper_size = 'letter' +#latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). -# latex_font_size = '10pt' +#latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]) +# (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "pyOpenSSL.tex", u"pyOpenSSL Documentation", authors, "manual"), + ('index', 'pyOpenSSL.tex', u'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 +#latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -# latex_use_parts = False +#latex_use_parts = False # If true, show page references after internal links. -# latex_show_pagerefs = False +#latex_show_pagerefs = False # If true, show URL addresses after external links. -# latex_show_urls = False +#latex_show_urls = False # Additional stuff for the LaTeX preamble. -# latex_preamble = '' +#latex_preamble = '' # Documents to append as an appendix to all manuals. -# latex_appendices = [] +#latex_appendices = [] # If false, no module index is generated. -# latex_domain_indices = True +#latex_domain_indices = True -# -- Options for manual page output ------------------------------------------- +# -- 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", u"pyOpenSSL Documentation", [authors], 1)] +man_pages = [ + ('index', 'pyopenssl', u'pyOpenSSL Documentation', + [authors], 1) +] intersphinx_mapping = { "https://docs.python.org/3": None, diff --git a/doc/index.rst b/doc/index.rst index 2d64d3b..d4c5e2e 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -20,6 +20,7 @@ Contents: api internals +There are also `examples in the pyOpenSSL repository <https://github.com/pyca/pyopenssl/tree/master/examples>`_ that may help you getting started. Meta diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..1e9116b --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,56 @@ +======== +Examples +======== + + +certgen.py -- Certificate generation module +=========================================== + +Example module with three functions: + +createKeyPair + Create a public/private key pair. + +createCertRequest + Create a certificate request. + +createCertificate + Create a certificate given a cert request. + +In fact, I created the certificates and keys in the 'simple' directory with the script ``mk_simple_certs.py``. + + +simple -- Simple client/server example +====================================== + +Start the server with:: + + python server.py PORT + +and start clients with:: + + python client.py HOST PORT + +The server is a simple echo server, anything a client sends, it sends back. + + +proxy.py -- Example of an SSL-enabled proxy +=========================================== + +The proxy example demonstrate how to use set_connect_state to start talking SSL over an already connected socket. + +Usage:: + + python proxy.py server[:port] proxy[:port] + +Contributed by Mihai Ibanescu + + +SecureXMLRPCServer.py -- SSL-enabled version of SimpleXMLRPCServer +================================================================== + +Acts exactly like `SimpleXMLRPCServer <https://docs.python.org/3/library/xmlrpc.server.html>`_ from the Python standard library, but uses secure connections. +The technique and classes should work for any SocketServer style server. +However, the code has not been extensively tested. + +Contributed by Michal Wallace diff --git a/examples/SecureXMLRPCServer.py b/examples/SecureXMLRPCServer.py new file mode 100644 index 0000000..56bfaea --- /dev/null +++ b/examples/SecureXMLRPCServer.py @@ -0,0 +1,115 @@ +""" +SecureXMLRPCServer module using pyOpenSSL 0.5 +Written 0907.2002 +by Michal Wallace +http://www.sabren.net/ + +This acts exactly like SimpleXMLRPCServer +from the standard python library, but +uses secure connections. The technique +and classes should work for any SocketServer +style server. However, the code has not +been extensively tested. + +This code is in the public domain. +It is provided AS-IS WITH NO WARRANTY WHATSOEVER. +""" + +import SimpleXMLRPCServer +import SocketServer +import os +import socket + +from OpenSSL import SSL + + +class SSLWrapper: + """ + This whole class exists just to filter out a parameter + passed in to the shutdown() method in SimpleXMLRPC.doPOST() + """ + def __init__(self, conn): + """ + Connection is not yet a new-style class, + so I'm making a proxy instead of subclassing. + """ + self.__dict__["conn"] = conn + + def __getattr__(self, name): + return getattr(self.__dict__["conn"], name) + + def __setattr__(self, name, value): + setattr(self.__dict__["conn"], name, value) + + def shutdown(self, how=1): + """ + SimpleXMLRpcServer.doPOST calls shutdown(1), + and Connection.shutdown() doesn't take + an argument. So we just discard the argument. + """ + self.__dict__["conn"].shutdown() + + def accept(self): + """ + This is the other part of the shutdown() workaround. + Since servers create new sockets, we have to infect + them with our magic. :) + """ + c, a = self.__dict__["conn"].accept() + return (SSLWrapper(c), a) + + +class SecureTCPServer(SocketServer.TCPServer): + """ + Just like TCPServer, but use a socket. + This really ought to let you specify the key and certificate files. + """ + def __init__(self, server_address, RequestHandlerClass): + SocketServer.BaseServer.__init__( + self, server_address, RequestHandlerClass + ) + + # Same as normal, but make it secure: + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.set_options(SSL.OP_NO_SSLv2) + + dir = os.curdir + ctx.use_privatekey_file(os.path.join(dir, 'server.pkey')) + ctx.use_certificate_file(os.path.join(dir, 'server.cert')) + + self.socket = SSLWrapper( + SSL.Connection( + ctx, socket.socket(self.address_family, self.socket_type) + ) + ) + self.server_bind() + self.server_activate() + + +class SecureXMLRPCRequestHandler( + SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): + def setup(self): + """ + We need to use socket._fileobject Because SSL.Connection + doesn't have a 'dup'. Not exactly sure WHY this is, but + this is backed up by comments in socket.py and SSL/connection.c + """ + self.connection = self.request # for doPOST + self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) + self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) + + +class SecureXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, + SecureTCPServer): + def __init__(self, addr, + requestHandler=SecureXMLRPCRequestHandler, + logRequests=1): + """ + This is the exact same code as SimpleXMLRPCServer.__init__ + except it calls SecureTCPServer.__init__ instead of plain + old TCPServer.__init__ + """ + self.funcs = {} + self.logRequests = logRequests + self.instance = None + SecureTCPServer.__init__(self, addr, requestHandler) diff --git a/examples/certgen.py b/examples/certgen.py new file mode 100644 index 0000000..7b70e98 --- /dev/null +++ b/examples/certgen.py @@ -0,0 +1,84 @@ +# -*- coding: latin-1 -*- +# +# Copyright (C) AB Strakt +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Certificate generation module. +""" + +from OpenSSL import crypto + +TYPE_RSA = crypto.TYPE_RSA +TYPE_DSA = crypto.TYPE_DSA + + +def createKeyPair(type, bits): + """ + Create a public/private key pair. + + Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA + bits - Number of bits to use in the key + Returns: The public/private key pair in a PKey object + """ + pkey = crypto.PKey() + pkey.generate_key(type, bits) + return pkey + + +def createCertRequest(pkey, digest="sha256", **name): + """ + Create a certificate request. + + Arguments: pkey - The key to associate with the request + digest - Digestion method to use for signing, default is sha256 + **name - The name of the subject of the request, possible + arguments are: + C - Country name + ST - State or province name + L - Locality name + O - Organization name + OU - Organizational unit name + CN - Common name + emailAddress - E-mail address + Returns: The certificate request in an X509Req object + """ + req = crypto.X509Req() + subj = req.get_subject() + + for key, value in name.items(): + setattr(subj, key, value) + + req.set_pubkey(pkey) + req.sign(pkey, digest) + return req + + +def createCertificate(req, issuerCertKey, serial, validityPeriod, + digest="sha256"): + """ + Generate a certificate given a certificate request. + + Arguments: req - Certificate request to use + issuerCert - The certificate of the issuer + issuerKey - The private key of the issuer + serial - Serial number for the certificate + notBefore - Timestamp (relative to now) when the certificate + starts being valid + notAfter - Timestamp (relative to now) when the certificate + stops being valid + digest - Digest method to use for signing, default is sha256 + Returns: The signed certificate in an X509 object + """ + issuerCert, issuerKey = issuerCertKey + notBefore, notAfter = validityPeriod + cert = crypto.X509() + cert.set_serial_number(serial) + cert.gmtime_adj_notBefore(notBefore) + cert.gmtime_adj_notAfter(notAfter) + cert.set_issuer(issuerCert.get_subject()) + cert.set_subject(req.get_subject()) + cert.set_pubkey(req.get_pubkey()) + cert.sign(issuerKey, digest) + return cert diff --git a/examples/mk_simple_certs.py b/examples/mk_simple_certs.py new file mode 100644 index 0000000..f0416cd --- /dev/null +++ b/examples/mk_simple_certs.py @@ -0,0 +1,50 @@ +""" +Create certificates and private keys for the 'simple' example. +""" + +from __future__ import print_function + +from OpenSSL import crypto +from certgen import ( + createKeyPair, + createCertRequest, + createCertificate, +) + +cakey = createKeyPair(crypto.TYPE_RSA, 2048) +careq = createCertRequest(cakey, CN='Certificate Authority') +# CA certificate is valid for five years. +cacert = createCertificate(careq, (careq, cakey), 0, (0, 60*60*24*365*5)) + +print('Creating Certificate Authority private key in "simple/CA.pkey"') +with open('simple/CA.pkey', 'w') as capkey: + capkey.write( + crypto.dump_privatekey(crypto.FILETYPE_PEM, cakey).decode('utf-8') + ) + +print('Creating Certificate Authority certificate in "simple/CA.cert"') +with open('simple/CA.cert', 'w') as ca: + ca.write( + crypto.dump_certificate(crypto.FILETYPE_PEM, cacert).decode('utf-8') + ) + +for (fname, cname) in [('client', 'Simple Client'), + ('server', 'Simple Server')]: + pkey = createKeyPair(crypto.TYPE_RSA, 2048) + req = createCertRequest(pkey, CN=cname) + # Certificates are valid for five years. + cert = createCertificate(req, (cacert, cakey), 1, (0, 60*60*24*365*5)) + + print('Creating Certificate %s private key in "simple/%s.pkey"' + % (fname, fname)) + with open('simple/%s.pkey' % (fname,), 'w') as leafpkey: + leafpkey.write( + crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey).decode('utf-8') + ) + + print('Creating Certificate %s certificate in "simple/%s.cert"' + % (fname, fname)) + with open('simple/%s.cert' % (fname,), 'w') as leafcert: + leafcert.write( + crypto.dump_certificate(crypto.FILETYPE_PEM, cert).decode('utf-8') + ) diff --git a/examples/proxy.py b/examples/proxy.py new file mode 100644 index 0000000..3be26f9 --- /dev/null +++ b/examples/proxy.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# +# This script demonstrates how one can use pyOpenSSL to speak SSL over an HTTP +# proxy +# The challenge here is to start talking SSL over an already connected socket +# +# Author: Mihai Ibanescu <misa@redhat.com> +# +# $Id: proxy.py,v 1.2 2004/07/22 12:01:25 martin Exp $ + +import sys +import socket +import string + +from OpenSSL import SSL + + +def usage(exit_code=0): + print "Usage: %s server[:port] proxy[:port]" % sys.argv[0] + print " Connects SSL to the specified server (port 443 by default)" + print " using the specified proxy (port 8080 by default)" + sys.exit(exit_code) + + +def main(): + # Command-line processing + if len(sys.argv) != 3: + usage(-1) + + server, proxy = sys.argv[1:3] + + run(split_host(server, 443), split_host(proxy, 8080)) + + +def split_host(hostname, default_port=80): + a = string.split(hostname, ':', 1) + if len(a) == 1: + a.append(default_port) + return a[0], int(a[1]) + + +# Connects to the server, through the proxy +def run(server, proxy): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(proxy) + except socket.error, e: + print "Unable to connect to %s:%s %s" % (proxy[0], proxy[1], str(e)) + sys.exit(-1) + + # Use the CONNECT method to get a connection to the actual server + s.send("CONNECT %s:%s HTTP/1.0\n\n" % (server[0], server[1])) + print "Proxy response: %s" % string.strip(s.recv(1024)) + + ctx = SSL.Context(SSL.SSLv23_METHOD) + conn = SSL.Connection(ctx, s) + + # Go to client mode + conn.set_connect_state() + + # start using HTTP + + conn.send("HEAD / HTTP/1.0\n\n") + print "Sever response:" + print "-" * 40 + while 1: + try: + buff = conn.recv(4096) + except SSL.ZeroReturnError: + # we're done + break + + print buff, + + +if __name__ == '__main__': + main() diff --git a/examples/simple/README b/examples/simple/README new file mode 100644 index 0000000..a072998 --- /dev/null +++ b/examples/simple/README @@ -0,0 +1,3 @@ +To use this example, first generate keys and certificates for both the +client and the server. You can do this with the script in the directory +above this one, mk_simple_certs.py. diff --git a/examples/simple/client.py b/examples/simple/client.py new file mode 100644 index 0000000..5662122 --- /dev/null +++ b/examples/simple/client.py @@ -0,0 +1,62 @@ +# -*- coding: latin-1 -*- +# +# Copyright (C) AB Strakt +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Simple SSL client, using blocking I/O +""" + +import os +import socket +import sys + +from OpenSSL import SSL, crypto + + +def verify_cb(conn, cert, errnum, depth, ok): + certsubject = crypto.X509Name(cert.get_subject()) + commonname = certsubject.commonName + print('Got certificate: ' + commonname) + return ok + + +if len(sys.argv) < 3: + print('Usage: python client.py HOST PORT') + sys.exit(1) + + +dir = os.path.dirname(sys.argv[0]) +if dir == '': + dir = os.curdir + + +# Initialize context +ctx = SSL.Context(SSL.SSLv23_METHOD) +ctx.set_options(SSL.OP_NO_SSLv2) +ctx.set_options(SSL.OP_NO_SSLv3) +ctx.set_verify(SSL.VERIFY_PEER, verify_cb) # Demand a certificate +ctx.use_privatekey_file(os.path.join(dir, 'client.pkey')) +ctx.use_certificate_file(os.path.join(dir, 'client.cert')) +ctx.load_verify_locations(os.path.join(dir, 'CA.cert')) + +# Set up client +sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) +sock.connect((sys.argv[1], int(sys.argv[2]))) + +while 1: + line = sys.stdin.readline() + if line == '': + break + try: + sock.send(line) + sys.stdout.write(sock.recv(1024).decode('utf-8')) + sys.stdout.flush() + except SSL.Error: + print('Connection died unexpectedly') + break + + +sock.shutdown() +sock.close() diff --git a/examples/simple/server.py b/examples/simple/server.py new file mode 100644 index 0000000..d25feb1 --- /dev/null +++ b/examples/simple/server.py @@ -0,0 +1,119 @@ +# -*- coding: latin-1 -*- +# +# Copyright (C) AB Strakt +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Simple echo server, using nonblocking I/O +""" + +from __future__ import print_function + +import os +import select +import socket +import sys + +from OpenSSL import SSL, crypto + + +def verify_cb(conn, cert, errnum, depth, ok): + certsubject = crypto.X509Name(cert.get_subject()) + commonname = certsubject.commonName + print('Got certificate: ' + commonname) + return ok + + +if len(sys.argv) < 2: + print('Usage: python server.py PORT') + sys.exit(1) + +dir = os.path.dirname(sys.argv[0]) +if dir == '': + dir = os.curdir + +# Initialize context +ctx = SSL.Context(SSL.SSLv23_METHOD) +ctx.set_options(SSL.OP_NO_SSLv2) +ctx.set_options(SSL.OP_NO_SSLv3) +ctx.set_verify( + SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb +) # Demand a certificate +ctx.use_privatekey_file(os.path.join(dir, 'server.pkey')) +ctx.use_certificate_file(os.path.join(dir, 'server.cert')) +ctx.load_verify_locations(os.path.join(dir, 'CA.cert')) + +# Set up server +server = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) +server.bind(('', int(sys.argv[1]))) +server.listen(3) +server.setblocking(0) + +clients = {} +writers = {} + + +def dropClient(cli, errors=None): + if errors: + print('Client %s left unexpectedly:' % (clients[cli],)) + print(' ', errors) + else: + print('Client %s left politely' % (clients[cli],)) + del clients[cli] + if cli in writers: + del writers[cli] + if not errors: + cli.shutdown() + cli.close() + + +while 1: + try: + r, w, _ = select.select( + [server] + list(clients.keys()), list(writers.keys()), [] + ) + except Exception: + break + + for cli in r: + if cli == server: + cli, addr = server.accept() + print('Connection from %s' % (addr,)) + clients[cli] = addr + + else: + try: + ret = cli.recv(1024).decode('utf-8') + except (SSL.WantReadError, + SSL.WantWriteError, + SSL.WantX509LookupError): + pass + except SSL.ZeroReturnError: + dropClient(cli) + except SSL.Error as errors: + dropClient(cli, errors) + else: + if cli not in writers: + writers[cli] = '' + writers[cli] = writers[cli] + ret + + for cli in w: + try: + ret = cli.send(writers[cli]) + except (SSL.WantReadError, + SSL.WantWriteError, + SSL.WantX509LookupError): + pass + except SSL.ZeroReturnError: + dropClient(cli) + except SSL.Error as errors: + dropClient(cli, errors) + else: + writers[cli] = writers[cli][ret:] + if writers[cli] == '': + del writers[cli] + +for cli in clients.keys(): + cli.close() +server.close() diff --git a/examples/sni/README b/examples/sni/README new file mode 100644 index 0000000..4c74eb5 --- /dev/null +++ b/examples/sni/README @@ -0,0 +1,19 @@ +This directory contains client and server examples for the "Server Name +Indication" (SNI) feature. + +Run server.py with no arguments. It will accept one client connection and +then exit. It has two certificates it can use, one for "example.invalid" +and another for "another.invalid". If a client indicates one of these names +to it, it will use the corresponding certificate for that connection (if a +client doesn't indicate a name or indicates another name, it won't try to +use any certificate). + +Run client.py with one argument, the server name to indicate. For example: + + $ python client.py example.invalid + Connecting... connected ('127.0.0.1', 8443) + Server subject is <X509Name object '/OU=Security/O=pyOpenSSL/CN=example.invalid/ST=New York/C=US/emailAddress=invalid@example.invalid/L=New York'> + $ + +Depending on what hostname is supplied, the server will select a different +certificate to use and the client output will be different. diff --git a/examples/sni/another.invalid.crt b/examples/sni/another.invalid.crt new file mode 100644 index 0000000..995e14c --- /dev/null +++ b/examples/sni/another.invalid.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5 +MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2Fub3RoZXIuaW52YWxpZDER +MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp +bnZhbGlkQGFub3RoZXIuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw +NjA2MTIyMTQyWhcNMTIwNjA1MTIyMTQyWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx +EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPYW5vdGhlci5pbnZhbGlkMREw +DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu +dmFsaWRAYW5vdGhlci5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEA7jUOM0EnH0/bvqyQfrGlZ5ROc29JWEq3wp7/ +n96cxQ/oSf5G6rlQ5ZYnDlp44csQOY3DIq5/7cRju/Qf5cZ03YMOjzYSi4ElS0+o +3Av/VgL/ssC6Z0PfQO4+NyXIQTn+cS6P6T65AVBdqn6Z5t0eY0wkU6QznpdJ/1c2 +a7gIYnUCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBqyrP1wmpTmfeZnoB7piJd+qIj +VHpCDRAZcdsxKUl/8PahjtWPMB0G5VaMwOoIGIlMxZ/LPKf44cA+QNEIXq8rohr2 +XFaA4t4X4aP7OmwQ4pa8mh4r86mP+vQU2iRJOqRYP+/gKaAqI2+ZbORZXJ7bewb5 +DTvvQRw2PRBf270h8g== +-----END CERTIFICATE----- diff --git a/examples/sni/another.invalid.key b/examples/sni/another.invalid.key new file mode 100644 index 0000000..8d955f6 --- /dev/null +++ b/examples/sni/another.invalid.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDuNQ4zQScfT9u+rJB+saVnlE5zb0lYSrfCnv+f3pzFD+hJ/kbq +uVDllicOWnjhyxA5jcMirn/txGO79B/lxnTdgw6PNhKLgSVLT6jcC/9WAv+ywLpn +Q99A7j43JchBOf5xLo/pPrkBUF2qfpnm3R5jTCRTpDOel0n/VzZruAhidQIDAQAB +AoGBAOGaJBHM8fWI17DVlKA5NVNNNaPEUW2qjjFoDuflmQpWD4UMqzOhQYm/VMwW +SYhnnr0zkw1kwUp6Bo87HX6sH37b1GeqIyp+b0Hqc+vLyiXPo0suqV23B9K8jjZ0 +6ap8h6hxpa5D1HtYKKDzWLhLJVtmtslxsvimR/CS+rmpUgBBAkEA+lJ2dXMDsUzB +xOpX8MLfQsl8XB5tx4ejmXGyNp/hmRFqFi38FFemJXX1YC3wL5jbQ2Ltz9rnbdnG +Xb/IWrn25QJBAPOcPua6xiNTWW5519JGaNgWdYnUgbj/ib8waLoElHp5Hl5DLuYX +y8U96Xl/wAE4aQnp5R/PS75tYrKZo79z9FECQQDALk1J8IpWNbLSRoRLkKEtulji +tG3d8VH1/WcwLuFZzhfffWB6Eay6N+yx8bLkJ/u2qZ4gpVRmbvqvgQ0GMp3NAkBE +FFczzeCPgLyOdjiNSCYGtYgVg7DZDXjmWFX8HkmMTIrjFu1lWiMVNS8pSD1VWflo +zte8Ywcs6Y7akLtFRtdxAkEA346J1/Zqtibez2TcjzCK+s9Ihwta23ZN2YTjo60o +sDZ5AVJwyLa7VFEzO/e9v2ytD7k9fCJjHcxIWIe8zj0dYA== +-----END RSA PRIVATE KEY----- diff --git a/examples/sni/client.py b/examples/sni/client.py new file mode 100644 index 0000000..428525b --- /dev/null +++ b/examples/sni/client.py @@ -0,0 +1,36 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +from sys import argv, stdout +from socket import socket + +from OpenSSL.SSL import TLSv1_METHOD, Context, Connection + + +def main(): + """ + Connect to an SNI-enabled server and request a specific hostname, specified + by argv[1], of it. + """ + if len(argv) < 2: + print 'Usage: %s <hostname>' % (argv[0],) + return 1 + + client = socket() + + print 'Connecting...', + stdout.flush() + client.connect(('127.0.0.1', 8443)) + print 'connected', client.getpeername() + + client_ssl = Connection(Context(TLSv1_METHOD), client) + client_ssl.set_connect_state() + client_ssl.set_tlsext_host_name(argv[1]) + client_ssl.do_handshake() + print 'Server subject is', client_ssl.get_peer_certificate().get_subject() + client_ssl.close() + + +if __name__ == '__main__': + import client + raise SystemExit(client.main()) diff --git a/examples/sni/example.invalid.crt b/examples/sni/example.invalid.crt new file mode 100644 index 0000000..b0cabac --- /dev/null +++ b/examples/sni/example.invalid.crt @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5 +MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2V4YW1wbGUuaW52YWxpZDER +MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp +bnZhbGlkQGV4YW1wbGUuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw +NjA2MTIyMTMzWhcNMTIwNjA1MTIyMTMzWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx +EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPZXhhbXBsZS5pbnZhbGlkMREw +DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu +dmFsaWRAZXhhbXBsZS5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq +hkiG9w0BAQEFAAOBjQAwgYkCgYEAwmLucR0IXvoGTOfzb2WJlHis2s/FFJfmYAKd +hq9bs+XzPeAPG0VQqAsy+om1gBOb8KPGtSet2SeNc25FU+QuwAza8uws7EaxD9b9 +CcarIh2X5LMcmiI/p34FuVGUSVsfc4QCTYFWGA0Mrw4jz9sGGeSEmTjVRnc3uAix +31orKScCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBxm8Qta5wYFmQ3l3EAne9+HaQ5 +gPStgox6STmyOGfRkybSePgOeKftOasaXpKboiNg6PJEkaFEnl9epNwS+8PIjQqv +mPiZdlrNIfw+YVWpqgcTAIzkhYFH0K4v6d5Wn2adNgd5KbrxYOjsr2w0ixQEtdW/ ++z1x/ngjc08EPqOIPQ== +-----END CERTIFICATE----- diff --git a/examples/sni/example.invalid.key b/examples/sni/example.invalid.key new file mode 100644 index 0000000..192e346 --- /dev/null +++ b/examples/sni/example.invalid.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDCYu5xHQhe+gZM5/NvZYmUeKzaz8UUl+ZgAp2Gr1uz5fM94A8b +RVCoCzL6ibWAE5vwo8a1J63ZJ41zbkVT5C7ADNry7CzsRrEP1v0JxqsiHZfksxya +Ij+nfgW5UZRJWx9zhAJNgVYYDQyvDiPP2wYZ5ISZONVGdze4CLHfWispJwIDAQAB +AoGBAL8L8qNTUHXgL68ITRZP6g71J5YKm/zoafA0wdOsp2lA+Hb4roAz+Nif4SOh +krPlEd9JZ7OF4vRJTlmDqDmSS2qY7hJuZpdrdvhdxaPGeX4uftC43thEzxLxPQHd +gCCxugbGJOHChjMPk06oC0w1q70ex3gWmki82Jt/5INV6Z6RAkEA4km0s0RvbVmW +AT12PROplCRE86eJNlLCVp2TJNl0LPZe5uWqaZZ8wBvfFd1PXEk/Qcpj4IotMZ5M +1Ai4zw2+6QJBANvo6R5yLRrY8/7YKw9Y/1bbSRLhGYok2Ur4fFz64G28wA1VI3yS +uXrJ7NjTVykfrBq59WEfh3a15P9g/TMAPY8CQQDdW3Z9iqtpj6IScnowgwR22wfs +RW4PCuP6cMhY2rMvrI3nVrDd+wzrrBgNPmF8iFZt2Drdkq1lBVJodGO8f9jJAj9O +K3yyVeOyp2wUKsMjsX8SYOCY1Ws+r9qNy8ZpRsSAPZgHJTx4C6/i9eQ7LuTMuXV0 +CqYu4AZHLGE6Zj+a4XsCQQC8Ken471EXuahfPcKTzsphuZnYZkoVUsFUxJFfqG+S +8k2Jo/4c+2NyyvVXhXu2at8kmu45c92BrCTXIvLEwtnn +-----END RSA PRIVATE KEY----- diff --git a/examples/sni/server.py b/examples/sni/server.py new file mode 100644 index 0000000..e0c159a --- /dev/null +++ b/examples/sni/server.py @@ -0,0 +1,65 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +from sys import stdout +from socket import SOL_SOCKET, SO_REUSEADDR, socket + +from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, load_certificate +from OpenSSL.SSL import TLSv1_METHOD, Context, Connection + + +def load(domain): + crt = open(domain + ".crt") + key = open(domain + ".key") + result = ( + load_privatekey(FILETYPE_PEM, key.read()), + load_certificate(FILETYPE_PEM, crt.read())) + crt.close() + key.close() + return result + + +def main(): + """ + Run an SNI-enabled server which selects between a few certificates in a + C{dict} based on the handshake request it receives from a client. + """ + port = socket() + port.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + port.bind(('', 8443)) + port.listen(3) + + print 'Accepting...', + stdout.flush() + server, addr = port.accept() + print 'accepted', addr + + server_context = Context(TLSv1_METHOD) + server_context.set_tlsext_servername_callback(pick_certificate) + + server_ssl = Connection(server_context, server) + server_ssl.set_accept_state() + server_ssl.do_handshake() + server.close() + + +certificates = { + "example.invalid": load("example.invalid"), + "another.invalid": load("another.invalid"), +} + + +def pick_certificate(connection): + try: + key, cert = certificates[connection.get_servername()] + except KeyError: + pass + else: + new_context = Context(TLSv1_METHOD) + new_context.use_privatekey(key) + new_context.use_certificate(cert) + connection.set_context(new_context) + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/leakcheck/context-info-callback.py b/leakcheck/context-info-callback.py new file mode 100644 index 0000000..6a3925c --- /dev/null +++ b/leakcheck/context-info-callback.py @@ -0,0 +1,97 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. +# +# Stress tester for thread-related bugs in global_info_callback in +# src/ssl/context.c. In 0.7 and earlier, this will somewhat reliably +# segfault or abort after a few dozen to a few thousand iterations on an SMP +# machine (generally not on a UP machine) due to uses of Python/C API +# without holding the GIL. + +from itertools import count +from threading import Thread +from socket import socket + +from OpenSSL.SSL import Context, TLSv1_METHOD, Connection, WantReadError +from OpenSSL.crypto import FILETYPE_PEM, load_certificate, load_privatekey + +cleartextPrivateKeyPEM = ( + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXAIBAAKBgQDaemNe1syksAbFFpF3aoOrZ18vB/IQNZrAjFqXPv9iieJm7+Tc\n" + "g+lA/v0qmoEKrpT2xfwxXmvZwBNM4ZhyRC3DPIFEyJV7/3IA1p5iuMY/GJI1VIgn\n" + "aikQCnrsyxtaRpsMBeZRniaVzcUJ+XnEdFGEjlo+k0xlwfVclDEMwgpXAQIDAQAB\n" + "AoGBALi0a7pMQqqgnriVAdpBVJveQtxSDVWi2/gZMKVZfzNheuSnv4amhtaKPKJ+\n" + "CMZtHkcazsE2IFvxRN/kgato9H3gJqq8nq2CkdpdLNVKBoxiCtkLfutdY4SQLtoY\n" + "USN7exk131pchsAJXYlR6mCW+ZP+E523cNwpPgsyKxVbmXSBAkEA9470fy2W0jFM\n" + "taZFslpntKSzbvn6JmdtjtvWrM1bBaeeqFiGBuQFYg46VaCUaeRWYw02jmYAsDYh\n" + "ZQavmXThaQJBAOHtlAQ0IJJEiMZr6vtVPH32fmbthSv1AUSYPzKqdlQrUnOXPQXu\n" + "z70cFoLG1TvPF5rBxbOkbQ/s8/ka5ZjPfdkCQCeC7YsO36+UpsWnUCBzRXITh4AC\n" + "7eYLQ/U1KUJTVF/GrQ/5cQrQgftwgecAxi9Qfmk4xqhbp2h4e0QAmS5I9WECQH02\n" + "0QwrX8nxFeTytr8pFGezj4a4KVCdb2B3CL+p3f70K7RIo9d/7b6frJI6ZL/LHQf2\n" + "UP4pKRDkgKsVDx7MELECQGm072/Z7vmb03h/uE95IYJOgY4nfmYs0QKA9Is18wUz\n" + "DpjfE33p0Ha6GO1VZRIQoqE24F8o5oimy3BEjryFuw4=\n" + "-----END RSA PRIVATE KEY-----\n") + + +cleartextCertificatePEM = ( + "-----BEGIN CERTIFICATE-----\n" + "MIICfTCCAeYCAQEwDQYJKoZIhvcNAQEEBQAwgYYxCzAJBgNVBAYTAlVTMRkwFwYD\n" + "VQQDExBweW9wZW5zc2wuc2YubmV0MREwDwYDVQQHEwhOZXcgWW9yazESMBAGA1UE\n" + "ChMJUHlPcGVuU1NMMREwDwYDVQQIEwhOZXcgWW9yazEQMA4GCSqGSIb3DQEJARYB\n" + "IDEQMA4GA1UECxMHVGVzdGluZzAeFw0wODAzMjUxOTA0MTNaFw0wOTAzMjUxOTA0\n" + "MTNaMIGGMQswCQYDVQQGEwJVUzEZMBcGA1UEAxMQcHlvcGVuc3NsLnNmLm5ldDER\n" + "MA8GA1UEBxMITmV3IFlvcmsxEjAQBgNVBAoTCVB5T3BlblNTTDERMA8GA1UECBMI\n" + "TmV3IFlvcmsxEDAOBgkqhkiG9w0BCQEWASAxEDAOBgNVBAsTB1Rlc3RpbmcwgZ8w\n" + "DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSwBsUWkXdqg6tnXy8H8hA1\n" + "msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nAE0zhmHJELcM8gUTIlXv/\n" + "cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXNxQn5ecR0UYSOWj6TTGXB\n" + "9VyUMQzCClcBAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAmm0Vzvv1O91WLl2LnF2P\n" + "q55LJdOnJbCCXIgxLdoVmvYAz1ZJq1eGKgKWI5QLgxiSzJLEU7KK//aVfiZzoCd5\n" + "RipBiEEMEV4eAY317bHPwPP+4Bj9t0l8AsDLseC5vLRHgxrLEu3bn08DYx6imB5Q\n" + "UBj849/xpszEM7BhwKE0GiQ=\n" + "-----END CERTIFICATE-----\n") + +count = count() +def go(): + port = socket() + port.bind(('', 0)) + port.listen(1) + + called = [] + def info(conn, where, ret): + print count.next() + called.append(None) + context = Context(TLSv1_METHOD) + context.set_info_callback(info) + context.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + context.use_privatekey( + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + + while 1: + client = socket() + client.setblocking(False) + client.connect_ex(port.getsockname()) + + clientSSL = Connection(Context(TLSv1_METHOD), client) + clientSSL.set_connect_state() + + server, ignored = port.accept() + server.setblocking(False) + + serverSSL = Connection(context, server) + serverSSL.set_accept_state() + + del called[:] + while not called: + for ssl in clientSSL, serverSSL: + try: + ssl.do_handshake() + except WantReadError: + pass + + +threads = [Thread(target=go, args=()) for i in xrange(2)] +for th in threads: + th.start() +for th in threads: + th.join() diff --git a/leakcheck/context-passphrase-callback.py b/leakcheck/context-passphrase-callback.py new file mode 100644 index 0000000..ba71655 --- /dev/null +++ b/leakcheck/context-passphrase-callback.py @@ -0,0 +1,34 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. +# +# Stress tester for thread-related bugs in global_passphrase_callback in +# src/ssl/context.c. In 0.7 and earlier, this will somewhat reliably +# segfault or abort after a few dozen to a few thousand iterations on an SMP +# machine (generally not on a UP machine) due to uses of Python/C API +# without holding the GIL. + +from itertools import count +from threading import Thread + +from OpenSSL.SSL import Context, TLSv1_METHOD +from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, PKey, dump_privatekey + +k = PKey() +k.generate_key(TYPE_RSA, 128) +file('pkey.pem', 'w').write(dump_privatekey(FILETYPE_PEM, k, "blowfish", "foobar")) + +count = count() +def go(): + def cb(a, b, c): + print count.next() + return "foobar" + c = Context(TLSv1_METHOD) + c.set_passwd_cb(cb) + while 1: + c.use_privatekey_file('pkey.pem') + +threads = [Thread(target=go, args=()) for i in xrange(2)] +for th in threads: + th.start() +for th in threads: + th.join() diff --git a/leakcheck/context-verify-callback.py b/leakcheck/context-verify-callback.py new file mode 100644 index 0000000..0ae586b --- /dev/null +++ b/leakcheck/context-verify-callback.py @@ -0,0 +1,99 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. +# +# Stress tester for thread-related bugs in global_verify_callback in +# src/ssl/context.c. This will reliably segfault if context.c isn't a +# PyThreadState management technique which is compatible with the approach used +# by ssl.c. + + +from itertools import count +from threading import Thread +from socket import socket + +from OpenSSL.SSL import Context, TLSv1_METHOD, VERIFY_PEER, Connection, WantReadError +from OpenSSL.crypto import FILETYPE_PEM, load_certificate, load_privatekey + +cleartextPrivateKeyPEM = ( + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIICXAIBAAKBgQDaemNe1syksAbFFpF3aoOrZ18vB/IQNZrAjFqXPv9iieJm7+Tc\n" + "g+lA/v0qmoEKrpT2xfwxXmvZwBNM4ZhyRC3DPIFEyJV7/3IA1p5iuMY/GJI1VIgn\n" + "aikQCnrsyxtaRpsMBeZRniaVzcUJ+XnEdFGEjlo+k0xlwfVclDEMwgpXAQIDAQAB\n" + "AoGBALi0a7pMQqqgnriVAdpBVJveQtxSDVWi2/gZMKVZfzNheuSnv4amhtaKPKJ+\n" + "CMZtHkcazsE2IFvxRN/kgato9H3gJqq8nq2CkdpdLNVKBoxiCtkLfutdY4SQLtoY\n" + "USN7exk131pchsAJXYlR6mCW+ZP+E523cNwpPgsyKxVbmXSBAkEA9470fy2W0jFM\n" + "taZFslpntKSzbvn6JmdtjtvWrM1bBaeeqFiGBuQFYg46VaCUaeRWYw02jmYAsDYh\n" + "ZQavmXThaQJBAOHtlAQ0IJJEiMZr6vtVPH32fmbthSv1AUSYPzKqdlQrUnOXPQXu\n" + "z70cFoLG1TvPF5rBxbOkbQ/s8/ka5ZjPfdkCQCeC7YsO36+UpsWnUCBzRXITh4AC\n" + "7eYLQ/U1KUJTVF/GrQ/5cQrQgftwgecAxi9Qfmk4xqhbp2h4e0QAmS5I9WECQH02\n" + "0QwrX8nxFeTytr8pFGezj4a4KVCdb2B3CL+p3f70K7RIo9d/7b6frJI6ZL/LHQf2\n" + "UP4pKRDkgKsVDx7MELECQGm072/Z7vmb03h/uE95IYJOgY4nfmYs0QKA9Is18wUz\n" + "DpjfE33p0Ha6GO1VZRIQoqE24F8o5oimy3BEjryFuw4=\n" + "-----END RSA PRIVATE KEY-----\n") + + +cleartextCertificatePEM = ( + "-----BEGIN CERTIFICATE-----\n" + "MIICfTCCAeYCAQEwDQYJKoZIhvcNAQEEBQAwgYYxCzAJBgNVBAYTAlVTMRkwFwYD\n" + "VQQDExBweW9wZW5zc2wuc2YubmV0MREwDwYDVQQHEwhOZXcgWW9yazESMBAGA1UE\n" + "ChMJUHlPcGVuU1NMMREwDwYDVQQIEwhOZXcgWW9yazEQMA4GCSqGSIb3DQEJARYB\n" + "IDEQMA4GA1UECxMHVGVzdGluZzAeFw0wODAzMjUxOTA0MTNaFw0wOTAzMjUxOTA0\n" + "MTNaMIGGMQswCQYDVQQGEwJVUzEZMBcGA1UEAxMQcHlvcGVuc3NsLnNmLm5ldDER\n" + "MA8GA1UEBxMITmV3IFlvcmsxEjAQBgNVBAoTCVB5T3BlblNTTDERMA8GA1UECBMI\n" + "TmV3IFlvcmsxEDAOBgkqhkiG9w0BCQEWASAxEDAOBgNVBAsTB1Rlc3RpbmcwgZ8w\n" + "DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSwBsUWkXdqg6tnXy8H8hA1\n" + "msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nAE0zhmHJELcM8gUTIlXv/\n" + "cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXNxQn5ecR0UYSOWj6TTGXB\n" + "9VyUMQzCClcBAgMBAAEwDQYJKoZIhvcNAQEEBQADgYEAmm0Vzvv1O91WLl2LnF2P\n" + "q55LJdOnJbCCXIgxLdoVmvYAz1ZJq1eGKgKWI5QLgxiSzJLEU7KK//aVfiZzoCd5\n" + "RipBiEEMEV4eAY317bHPwPP+4Bj9t0l8AsDLseC5vLRHgxrLEu3bn08DYx6imB5Q\n" + "UBj849/xpszEM7BhwKE0GiQ=\n" + "-----END CERTIFICATE-----\n") + +count = count() +def go(): + port = socket() + port.bind(('', 0)) + port.listen(1) + + called = [] + def info(*args): + print count.next() + called.append(None) + return 1 + context = Context(TLSv1_METHOD) + context.set_verify(VERIFY_PEER, info) + context.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + context.use_privatekey( + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + + while 1: + client = socket() + client.setblocking(False) + client.connect_ex(port.getsockname()) + + clientSSL = Connection(context, client) + clientSSL.set_connect_state() + + server, ignored = port.accept() + server.setblocking(False) + + serverSSL = Connection(context, server) + serverSSL.set_accept_state() + + del called[:] + while not called: + for ssl in clientSSL, serverSSL: + try: + ssl.send('foo') + except WantReadError, e: + pass + + +threads = [Thread(target=go, args=()) for i in xrange(2)] +for th in threads: + th.start() +for th in threads: + th.join() + diff --git a/leakcheck/crypto.py b/leakcheck/crypto.py new file mode 100644 index 0000000..ca79b7c --- /dev/null +++ b/leakcheck/crypto.py @@ -0,0 +1,183 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +import sys + +from OpenSSL.crypto import ( + FILETYPE_PEM, TYPE_DSA, Error, PKey, X509, load_privatekey, CRL, Revoked, + get_elliptic_curves, _X509_REVOKED_dup) + +from OpenSSL._util import lib as _lib + + + +class BaseChecker(object): + def __init__(self, iterations): + self.iterations = iterations + + + +class Checker_X509_get_pubkey(BaseChecker): + """ + Leak checks for L{X509.get_pubkey}. + """ + def check_exception(self): + """ + Call the method repeatedly such that it will raise an exception. + """ + for i in xrange(self.iterations): + cert = X509() + try: + cert.get_pubkey() + except Error: + pass + + + def check_success(self): + """ + Call the method repeatedly such that it will return a PKey object. + """ + small = xrange(3) + for i in xrange(self.iterations): + key = PKey() + key.generate_key(TYPE_DSA, 256) + for i in small: + cert = X509() + cert.set_pubkey(key) + for i in small: + cert.get_pubkey() + + + +class Checker_load_privatekey(BaseChecker): + """ + Leak checks for :py:obj:`load_privatekey`. + """ + ENCRYPTED_PEM = """\ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: BF-CBC,3763C340F9B5A1D0 + +a/DO10mLjHLCAOG8/Hc5Lbuh3pfjvcTZiCexShP+tupkp0VxW2YbZjML8uoXrpA6 +fSPUo7cEC+r96GjV03ZIVhjmsxxesdWMpfkzXRpG8rUbWEW2KcCJWdSX8bEkuNW3 +uvAXdXZwiOrm56ANDo/48gj27GcLwnlA8ld39+ylAzkUJ1tcMVzzTjfcyd6BMFpR +Yjg23ikseug6iWEsZQormdl0ITdYzmFpM+YYsG7kmmmi4UjCEYfb9zFaqJn+WZT2 +qXxmo2ZPFzmEVkuB46mf5GCqMwLRN2QTbIZX2+Dljj1Hfo5erf5jROewE/yzcTwO +FCB5K3c2kkTv2KjcCAimjxkE+SBKfHg35W0wB0AWkXpVFO5W/TbHg4tqtkpt/KMn +/MPnSxvYr/vEqYMfW4Y83c45iqK0Cyr2pwY60lcn8Kk= +-----END RSA PRIVATE KEY----- +""" + def check_load_privatekey_callback(self): + """ + Call the function with an encrypted PEM and a passphrase callback. + """ + for i in xrange(self.iterations * 10): + load_privatekey( + FILETYPE_PEM, self.ENCRYPTED_PEM, lambda *args: "hello, secret") + + + def check_load_privatekey_callback_incorrect(self): + """ + Call the function with an encrypted PEM and a passphrase callback which + returns the wrong passphrase. + """ + for i in xrange(self.iterations * 10): + try: + load_privatekey( + FILETYPE_PEM, self.ENCRYPTED_PEM, + lambda *args: "hello, public") + except Error: + pass + + + def check_load_privatekey_callback_wrong_type(self): + """ + Call the function with an encrypted PEM and a passphrase callback which + returns a non-string. + """ + for i in xrange(self.iterations * 10): + try: + load_privatekey( + FILETYPE_PEM, self.ENCRYPTED_PEM, + lambda *args: {}) + except ValueError: + pass + + + +class Checker_CRL(BaseChecker): + """ + Leak checks for L{CRL.add_revoked} and L{CRL.get_revoked}. + """ + def check_add_revoked(self): + """ + Call the add_revoked method repeatedly on an empty CRL. + """ + for i in xrange(self.iterations * 200): + CRL().add_revoked(Revoked()) + + + def check_get_revoked(self): + """ + Create a CRL object with 100 Revoked objects, then call the + get_revoked method repeatedly. + """ + crl = CRL() + for i in xrange(100): + crl.add_revoked(Revoked()) + for i in xrange(self.iterations): + crl.get_revoked() + + + +class Checker_X509_REVOKED_dup(BaseChecker): + """ + Leak checks for :py:obj:`_X509_REVOKED_dup`. + """ + def check_X509_REVOKED_dup(self): + """ + Copy an empty Revoked object repeatedly. The copy is not garbage + collected, therefore it needs to be manually freed. + """ + for i in xrange(self.iterations * 100): + revoked_copy = _X509_REVOKED_dup(Revoked()._revoked) + _lib.X509_REVOKED_free(revoked_copy) + + + +class Checker_EllipticCurve(BaseChecker): + """ + Leak checks for :py:obj:`_EllipticCurve`. + """ + def check_to_EC_KEY(self): + """ + Repeatedly create an EC_KEY* from an :py:obj:`_EllipticCurve`. The + structure should be automatically garbage collected. + """ + curves = get_elliptic_curves() + if curves: + curve = next(iter(curves)) + for i in xrange(self.iterations * 1000): + curve._to_EC_KEY() + + +def vmsize(): + return [x for x in file('/proc/self/status').readlines() if 'VmSize' in x] + + +def main(iterations='1000'): + iterations = int(iterations) + for klass in globals(): + if klass.startswith('Checker_'): + klass = globals()[klass] + print klass + checker = klass(iterations) + for meth in dir(checker): + if meth.startswith('check_'): + print '\t', meth, vmsize(), '...', + getattr(checker, meth)() + print vmsize() + + +if __name__ == '__main__': + main(*sys.argv[1:]) diff --git a/leakcheck/dhparam.pem b/leakcheck/dhparam.pem new file mode 100644 index 0000000..9d33a4a --- /dev/null +++ b/leakcheck/dhparam.pem @@ -0,0 +1,4 @@ +-----BEGIN DH PARAMETERS----- +MEYCQQDM2LbvAjF5ahXHOUdDR09Vw/7kxjF/euWhNKBqUQQYT7FDSAMCCMq+Jhno +BKxWEDhlxR1Q1VZ4H/NVTAGtWai7AgEC +-----END DH PARAMETERS----- diff --git a/leakcheck/thread-crash.py b/leakcheck/thread-crash.py new file mode 100644 index 0000000..a1ebbdd --- /dev/null +++ b/leakcheck/thread-crash.py @@ -0,0 +1,71 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. +# +# Stress tester for thread-related bugs in ssl_Connection_send and +# ssl_Connection_recv in src/ssl/connection.c for usage of a single +# Connection object simultaneously in multiple threads. In 0.7 and earlier, +# this will somewhat reliably cause Python to abort with a "tstate mix-up" +# almost immediately, due to the incorrect sharing between threads of the +# `tstate` field of the connection object. + + +from socket import socket +from threading import Thread + +from OpenSSL.SSL import Connection, Context, TLSv1_METHOD + +def send(conn): + while 1: + for i in xrange(1024 * 32): + conn.send('x') + print 'Sent 32KB on', hex(id(conn)) + + +def recv(conn): + while 1: + for i in xrange(1024 * 64): + conn.recv(1) + print 'Received 64KB on', hex(id(conn)) + + +def main(): + port = socket() + port.bind(('', 0)) + port.listen(5) + + client = socket() + client.setblocking(False) + client.connect_ex(port.getsockname()) + client.setblocking(True) + + server = port.accept()[0] + + clientCtx = Context(TLSv1_METHOD) + clientCtx.set_cipher_list('ALL:ADH') + clientCtx.load_tmp_dh('dhparam.pem') + + sslClient = Connection(clientCtx, client) + sslClient.set_connect_state() + + serverCtx = Context(TLSv1_METHOD) + serverCtx.set_cipher_list('ALL:ADH') + serverCtx.load_tmp_dh('dhparam.pem') + + sslServer = Connection(serverCtx, server) + sslServer.set_accept_state() + + t1 = Thread(target=send, args=(sslClient,)) + t2 = Thread(target=send, args=(sslServer,)) + t3 = Thread(target=recv, args=(sslClient,)) + t4 = Thread(target=recv, args=(sslServer,)) + + t1.start() + t2.start() + t3.start() + t4.start() + t1.join() + t2.join() + t3.join() + t4.join() + +main() diff --git a/leakcheck/thread-key-gen.py b/leakcheck/thread-key-gen.py new file mode 100644 index 0000000..62e1a58 --- /dev/null +++ b/leakcheck/thread-key-gen.py @@ -0,0 +1,38 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. +# +# Stress tester for thread-related bugs in RSA and DSA key generation. 0.12 and +# older held the GIL during these operations. Subsequent versions release it +# during them. + +from threading import Thread + +from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, PKey + +def generate_rsa(): + keys = [] + for i in range(100): + key = PKey() + key.generate_key(TYPE_RSA, 1024) + keys.append(key) + +def generate_dsa(): + keys = [] + for i in range(100): + key = PKey() + key.generate_key(TYPE_DSA, 512) + keys.append(key) + + +def main(): + threads = [] + for i in range(3): + t = Thread(target=generate_rsa, args=()) + threads.append(t) + t = Thread(target=generate_dsa, args=()) + threads.append(t) + + for t in threads: + t.start() + +main() diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index ff6e2bb..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,4 +0,0 @@ -[tool.black] -line-length = 79 -target-version = ["py27"] - diff --git a/rpm/build_script b/rpm/build_script new file mode 100644 index 0000000..1b0276b --- /dev/null +++ b/rpm/build_script @@ -0,0 +1 @@ +make -C doc text html @@ -36,7 +36,8 @@ def find_meta(meta): Extract __*meta*__ from META_FILE. """ meta_match = re.search( - r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M + r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), + META_FILE, re.M ) if meta_match: return meta_match.group(1) @@ -45,17 +46,13 @@ def find_meta(meta): 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" + 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) @@ -67,39 +64,49 @@ if __name__ == "__main__": long_description=LONG, author=find_meta("author"), author_email=find_meta("email"), + maintainer="Hynek Schlawack", + maintainer_email="hs@ox.cx", url=URI, 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 :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - "Topic :: Security :: Cryptography", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: System :: Networking", + '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 :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Security :: Cryptography', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: System :: Networking', ], - python_requires=">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", + packages=find_packages(where="src"), package_dir={"": "src"}, install_requires=[ # Fix cryptographyMinimum in tox.ini when changing this! - "cryptography>=3.2", - "six>=1.5.2", + "cryptography>=2.3", + "six>=1.5.2" ], extras_require={ - "test": ["flaky", "pretend", "pytest>=3.0.1"], - "docs": ["sphinx", "sphinx_rtd_theme"], + "test": [ + "flaky", + "pretend", + "pytest>=3.0.1", + ], + "docs": [ + "sphinx", + "sphinx_rtd_theme", + ] }, ) diff --git a/src/OpenSSL/Android.bp b/src/OpenSSL/Android.bp index 81aa7e6..75c431e 100644 --- a/src/OpenSSL/Android.bp +++ b/src/OpenSSL/Android.bp @@ -11,15 +11,6 @@ // 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. -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "external_python_pyopenssl_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["external_python_pyopenssl_license"], -} - python_library { name: "py-pyopenssl", host_supported: true, diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py index e5fea1e..0687fc3 100644 --- a/src/OpenSSL/SSL.py +++ b/src/OpenSSL/SSL.py @@ -6,13 +6,16 @@ from itertools import count, chain from weakref import WeakValueDictionary from errno import errorcode -from six import integer_types, int2byte, indexbytes +from cryptography.utils import deprecated + +from six import ( + binary_type as _binary_type, integer_types as integer_types, int2byte, + indexbytes) from OpenSSL._util import ( UNSPECIFIED as _UNSPECIFIED, exception_from_error_queue as _exception_from_error_queue, ffi as _ffi, - from_buffer as _from_buffer, lib as _lib, make_assert as _make_assert, native as _native, @@ -22,108 +25,99 @@ from OpenSSL._util import ( ) from OpenSSL.crypto import ( - FILETYPE_PEM, - _PassphraseHelper, - PKey, - X509Name, - X509, - X509Store, -) + FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store) __all__ = [ - "OPENSSL_VERSION_NUMBER", - "SSLEAY_VERSION", - "SSLEAY_CFLAGS", - "SSLEAY_PLATFORM", - "SSLEAY_DIR", - "SSLEAY_BUILT_ON", - "SENT_SHUTDOWN", - "RECEIVED_SHUTDOWN", - "SSLv2_METHOD", - "SSLv3_METHOD", - "SSLv23_METHOD", - "TLSv1_METHOD", - "TLSv1_1_METHOD", - "TLSv1_2_METHOD", - "OP_NO_SSLv2", - "OP_NO_SSLv3", - "OP_NO_TLSv1", - "OP_NO_TLSv1_1", - "OP_NO_TLSv1_2", - "OP_NO_TLSv1_3", - "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", - "SSLeay_version", - "Session", - "Context", - "Connection", + 'OPENSSL_VERSION_NUMBER', + 'SSLEAY_VERSION', + 'SSLEAY_CFLAGS', + 'SSLEAY_PLATFORM', + 'SSLEAY_DIR', + 'SSLEAY_BUILT_ON', + 'SENT_SHUTDOWN', + 'RECEIVED_SHUTDOWN', + 'SSLv2_METHOD', + 'SSLv3_METHOD', + 'SSLv23_METHOD', + 'TLSv1_METHOD', + 'TLSv1_1_METHOD', + 'TLSv1_2_METHOD', + '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', + 'SSLeay_version', + 'Session', + 'Context', + 'Connection' ] try: _buffer = buffer except NameError: - class _buffer(object): pass - OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER SSLEAY_VERSION = _lib.SSLEAY_VERSION SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS @@ -146,10 +140,6 @@ OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3 OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1 OP_NO_TLSv1_1 = _lib.SSL_OP_NO_TLSv1_1 OP_NO_TLSv1_2 = _lib.SSL_OP_NO_TLSv1_2 -try: - OP_NO_TLSv1_3 = _lib.SSL_OP_NO_TLSv1_3 -except AttributeError: - pass MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS @@ -201,6 +191,17 @@ SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL SSL_ST_CONNECT = _lib.SSL_ST_CONNECT SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT SSL_ST_MASK = _lib.SSL_ST_MASK +if _lib.Cryptography_HAS_SSL_ST: + SSL_ST_INIT = _lib.SSL_ST_INIT + SSL_ST_BEFORE = _lib.SSL_ST_BEFORE + SSL_ST_OK = _lib.SSL_ST_OK + SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE + __all__.extend([ + 'SSL_ST_INIT', + 'SSL_ST_BEFORE', + 'SSL_ST_OK', + 'SSL_ST_RENEGOTIATE', + ]) SSL_CB_LOOP = _lib.SSL_CB_LOOP SSL_CB_EXIT = _lib.SSL_CB_EXIT @@ -329,11 +330,97 @@ class _VerifyHelper(_CallbackExceptionHelper): return 0 self.callback = _ffi.callback( - "int (*)(int, X509_STORE_CTX *)", wrapper + "int (*)(int, X509_STORE_CTX *)", wrapper) + + +class _NpnAdvertiseHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as an NPN advertisement callback. + """ + + def __init__(self, callback): + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, out, outlen, arg): + try: + conn = Connection._reverse_mapping[ssl] + protos = callback(conn) + + # Join the protocols into a Python bytestring, length-prefixing + # each element. + protostr = b''.join( + chain.from_iterable((int2byte(len(p)), p) for p in protos) + ) + + # Save our callback arguments on the connection object. This is + # done to make sure that they don't get freed before OpenSSL + # uses them. Then, return them appropriately in the output + # parameters. + conn._npn_advertise_callback_args = [ + _ffi.new("unsigned int *", len(protostr)), + _ffi.new("unsigned char[]", protostr), + ] + outlen[0] = conn._npn_advertise_callback_args[0][0] + out[0] = conn._npn_advertise_callback_args[1] + return 0 + except Exception as e: + self._problems.append(e) + return 2 # SSL_TLSEXT_ERR_ALERT_FATAL + + self.callback = _ffi.callback( + "int (*)(SSL *, const unsigned char **, unsigned int *, void *)", + wrapper ) -NO_OVERLAPPING_PROTOCOLS = object() +class _NpnSelectHelper(_CallbackExceptionHelper): + """ + Wrap a callback such that it can be used as an NPN selection callback. + """ + + def __init__(self, callback): + _CallbackExceptionHelper.__init__(self) + + @wraps(callback) + def wrapper(ssl, out, outlen, in_, inlen, arg): + try: + conn = Connection._reverse_mapping[ssl] + + # The string passed to us is actually made up of multiple + # length-prefixed bytestrings. We need to split that into a + # list. + instr = _ffi.buffer(in_, inlen)[:] + protolist = [] + while instr: + length = indexbytes(instr, 0) + proto = instr[1:length + 1] + protolist.append(proto) + instr = instr[length + 1:] + + # Call the callback + outstr = callback(conn, protolist) + + # Save our callback arguments on the connection object. This is + # done to make sure that they don't get freed before OpenSSL + # uses them. Then, return them appropriately in the output + # parameters. + conn._npn_select_callback_args = [ + _ffi.new("unsigned char *", len(outstr)), + _ffi.new("unsigned char[]", outstr), + ] + outlen[0] = conn._npn_select_callback_args[0][0] + out[0] = conn._npn_select_callback_args[1] + return 0 + except Exception as e: + self._problems.append(e) + return 2 # SSL_TLSEXT_ERR_ALERT_FATAL + + self.callback = _ffi.callback( + ("int (*)(SSL *, unsigned char **, unsigned char *, " + "const unsigned char *, unsigned int, void *)"), + wrapper + ) class _ALPNSelectHelper(_CallbackExceptionHelper): @@ -356,44 +443,34 @@ class _ALPNSelectHelper(_CallbackExceptionHelper): protolist = [] while instr: encoded_len = indexbytes(instr, 0) - proto = instr[1 : encoded_len + 1] + proto = instr[1:encoded_len + 1] protolist.append(proto) - instr = instr[encoded_len + 1 :] + 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." - ) + outstr = callback(conn, protolist) + + if not isinstance(outstr, _binary_type): + raise TypeError("ALPN callback must return a bytestring.") # 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), + _ffi.new("unsigned char *", len(outstr)), + _ffi.new("unsigned char[]", outstr), ] 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 + return 0 except Exception as e: self._problems.append(e) - return _lib.SSL_TLSEXT_ERR_ALERT_FATAL + return 2 # SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( - ( - "int (*)(SSL *, unsigned char **, unsigned char *, " - "const unsigned char *, unsigned int, void *)" - ), - wrapper, + ("int (*)(SSL *, unsigned char **, unsigned char *, " + "const unsigned char *, unsigned int, void *)"), + wrapper ) @@ -436,7 +513,7 @@ class _OCSPServerCallbackHelper(_CallbackExceptionHelper): # Call the callback. ocsp_data = callback(conn, data) - if not isinstance(ocsp_data, bytes): + if not isinstance(ocsp_data, _binary_type): raise TypeError("OCSP callback must return a bytestring.") # If the OCSP data was provided, we will pass it to OpenSSL. @@ -505,7 +582,7 @@ class _OCSPClientCallbackHelper(_CallbackExceptionHelper): ocsp_len = _lib.SSL_get_tlsext_status_ocsp_resp(ssl, ocsp_ptr) if ocsp_len < 0: # No OCSP data. - ocsp_data = b"" + ocsp_data = b'' else: # Copy the OCSP data, then pass it to the callback. ocsp_data = _ffi.buffer(ocsp_ptr[0], ocsp_len)[:] @@ -537,8 +614,7 @@ def _asFileDescriptor(obj): 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,) - ) + "file descriptor cannot be a negative integer (%i)" % (fd,)) return fd @@ -562,14 +638,11 @@ def _make_requires(flag, error): ``Cryptography_HAS_NEXTPROTONEG``. :param error: The string to be used in the exception if the flag is false. """ - def _requires_decorator(func): if not flag: - @wraps(func) def explode(*args, **kwargs): raise NotImplementedError(error) - return explode else: return func @@ -577,13 +650,18 @@ def _make_requires(flag, error): return _requires_decorator +_requires_npn = _make_requires( + _lib.Cryptography_HAS_NEXTPROTONEG, "NPN not available" +) + + _requires_alpn = _make_requires( _lib.Cryptography_HAS_ALPN, "ALPN not available" ) -_requires_keylog = _make_requires( - getattr(_lib, "Cryptography_HAS_KEYLOG", None), "Key logging not available" +_requires_sni = _make_requires( + _lib.Cryptography_HAS_TLSEXT_HOSTNAME, "SNI not available" ) @@ -595,7 +673,6 @@ class Session(object): .. versionadded:: 0.14 """ - pass @@ -607,7 +684,6 @@ class Context(object): :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or TLSv1_METHOD. """ - _methods = { SSLv2_METHOD: "SSLv2_method", SSLv3_METHOD: "SSLv3_method", @@ -619,8 +695,7 @@ class Context(object): _methods = dict( (identifier, getattr(_lib, name)) for (identifier, name) in _methods.items() - if getattr(_lib, name, None) is not None - ) + if getattr(_lib, name, None) is not None) def __init__(self, method): if not isinstance(method, integer_types): @@ -638,11 +713,14 @@ class Context(object): _openssl_assert(context != _ffi.NULL) context = _ffi.gc(context, _lib.SSL_CTX_free) - # Set SSL_CTX_set_ecdh_auto so that the ECDH curve will be - # auto-selected. This function was added in 1.0.2 and made a noop in - # 1.1.0+ (where it is set automatically). - res = _lib.SSL_CTX_set_ecdh_auto(context, 1) - _openssl_assert(res == 1) + # If SSL_CTX_set_ecdh_auto is available then set it so the ECDH curve + # will be auto-selected. This function was added in 1.0.2 and made a + # noop in 1.1.0+ (where it is set automatically). + try: + res = _lib.SSL_CTX_set_ecdh_auto(context, 1) + _openssl_assert(res == 1) + except AttributeError: + pass self._context = context self._passphrase_helper = None @@ -651,9 +729,12 @@ class Context(object): self._verify_helper = None self._verify_callback = None self._info_callback = None - self._keylog_callback = None self._tlsext_servername_callback = None self._app_data = None + self._npn_advertise_helper = None + self._npn_advertise_callback = None + self._npn_select_helper = None + self._npn_select_callback = None self._alpn_select_helper = None self._alpn_select_callback = None self._ocsp_helper = None @@ -698,10 +779,8 @@ class Context(object): @wraps(callback) def wrapper(size, verify, userdata): return callback(size, verify, self._passphrase_userdata) - return _PassphraseHelper( - FILETYPE_PEM, wrapper, more_args=True, truncate=True - ) + FILETYPE_PEM, wrapper, more_args=True, truncate=True) def set_passwd_cb(self, callback, userdata=None): """ @@ -728,8 +807,7 @@ class Context(object): 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._context, self._passphrase_callback) self._passphrase_userdata = userdata def set_default_verify_paths(self): @@ -759,9 +837,9 @@ class Context(object): # 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" - ) + 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") @@ -772,12 +850,13 @@ class Context(object): # to the exact values we use in our manylinux1 builds. If they are # then we know to load the fallbacks if ( - default_dir == _CRYPTOGRAPHY_MANYLINUX1_CA_DIR - and default_file == _CRYPTOGRAPHY_MANYLINUX1_CA_FILE + default_dir == _CRYPTOGRAPHY_MANYLINUX1_CA_DIR and + default_file == _CRYPTOGRAPHY_MANYLINUX1_CA_FILE ): # This is manylinux1, let's load our fallback paths self._fallback_default_verify_paths( - _CERTIFICATE_FILE_LOCATIONS, _CERTIFICATE_PATH_LOCATIONS + _CERTIFICATE_FILE_LOCATIONS, + _CERTIFICATE_PATH_LOCATIONS ) def _check_env_vars_set(self, dir_env_var, file_env_var): @@ -787,8 +866,8 @@ class Context(object): :return: bool """ return ( - os.environ.get(file_env_var) is not None - or os.environ.get(dir_env_var) is not None + 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, dir_path): @@ -906,8 +985,7 @@ class Context(object): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_PrivateKey_file( - self._context, keyfile, filetype - ) + self._context, keyfile, filetype) if not use_result: self._raise_passphrase_exception() @@ -963,8 +1041,11 @@ class Context(object): """ buf = _text_to_bytes_and_warn("buf", buf) _openssl_assert( - _lib.SSL_CTX_set_session_id_context(self._context, buf, len(buf)) - == 1 + _lib.SSL_CTX_set_session_id_context( + self._context, + buf, + len(buf), + ) == 1 ) def set_session_cache_mode(self, mode): @@ -994,22 +1075,21 @@ class Context(object): """ return _lib.SSL_CTX_get_session_cache_mode(self._context) - def set_verify(self, mode, callback=None): + def set_verify(self, mode, callback): """ - Set the verification flags for this Context object to *mode* and - specify that *callback* should be used for verification callbacks. + et 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. + :param callback: The Python 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. :return: None See SSL_CTX_set_verify(3SSL) for further details. @@ -1017,17 +1097,12 @@ class Context(object): if not isinstance(mode, integer_types): 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") + 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) + 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): """ @@ -1078,8 +1153,7 @@ class Context(object): 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) + _lib.SSL_CTX_set_tmp_dh(self._context, dh) def set_tmp_ecdh(self, curve): """ @@ -1117,20 +1191,13 @@ class Context(object): # 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", - ), - ], - ) + _openssl_assert( + tmpconn.get_cipher_list() != [ + 'TLS_AES_256_GCM_SHA384', + 'TLS_CHACHA20_POLY1305_SHA256', + 'TLS_AES_128_GCM_SHA256' + ] + ) def set_client_ca_list(self, certificate_authorities): """ @@ -1153,7 +1220,9 @@ class Context(object): if not isinstance(ca_name, X509Name): raise TypeError( "client CAs must be X509Name objects, not %s " - "objects" % (type(ca_name).__name__,) + "objects" % ( + type(ca_name).__name__, + ) ) copy = _lib.X509_NAME_dup(ca_name._name) _openssl_assert(copy != _ffi.NULL) @@ -1184,8 +1253,7 @@ class Context(object): raise TypeError("certificate_authority must be an X509 instance") add_result = _lib.SSL_CTX_add_client_CA( - self._context, certificate_authority._x509 - ) + self._context, certificate_authority._x509) _openssl_assert(add_result == 1) def set_timeout(self, timeout): @@ -1223,41 +1291,13 @@ class Context(object): function call. :return: None """ - @wraps(callback) def wrapper(ssl, where, return_code): callback(Connection._reverse_mapping[ssl], where, return_code) - self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper - ) + "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): - """ - 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): - 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): """ Get the application data (supplied via :meth:`set_app_data()`) @@ -1318,6 +1358,7 @@ class Context(object): return _lib.SSL_CTX_set_mode(self._context, mode) + @_requires_sni def set_tlsext_servername_callback(self, callback): """ Specify a callback function to be called when clients specify a server @@ -1328,18 +1369,15 @@ class Context(object): .. versionadded:: 0.13 """ - @wraps(callback) def wrapper(ssl, alert, arg): callback(Connection._reverse_mapping[ssl]) return 0 self._tlsext_servername_callback = _ffi.callback( - "int (*)(SSL *, int *, void *)", wrapper - ) + "int (*)(SSL *, int *, void *)", wrapper) _lib.SSL_CTX_set_tlsext_servername_callback( - self._context, self._tlsext_servername_callback - ) + self._context, self._tlsext_servername_callback) def set_tlsext_use_srtp(self, profiles): """ @@ -1356,6 +1394,43 @@ class Context(object): _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0 ) + @_requires_npn + def set_npn_advertise_callback(self, callback): + """ + Specify a callback function that will be called when offering `Next + Protocol Negotiation + <https://technotes.googlecode.com/git/nextprotoneg.html>`_ as a server. + + :param callback: The callback function. It will be invoked with one + argument, the :class:`Connection` instance. It should return a + list of bytestrings representing the advertised protocols, like + ``[b'http/1.1', b'spdy/2']``. + + .. versionadded:: 0.15 + """ + self._npn_advertise_helper = _NpnAdvertiseHelper(callback) + self._npn_advertise_callback = self._npn_advertise_helper.callback + _lib.SSL_CTX_set_next_protos_advertised_cb( + self._context, self._npn_advertise_callback, _ffi.NULL) + + @_requires_npn + def set_npn_select_callback(self, callback): + """ + Specify a callback function that will be called when a server offers + Next Protocol Negotiation options. + + :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 should return + one of those bytestrings, the chosen protocol. + + .. versionadded:: 0.15 + """ + self._npn_select_helper = _NpnSelectHelper(callback) + self._npn_select_callback = self._npn_select_helper.callback + _lib.SSL_CTX_set_next_proto_select_cb( + self._context, self._npn_select_callback, _ffi.NULL) + @_requires_alpn def set_alpn_protos(self, protos): """ @@ -1369,7 +1444,7 @@ class Context(object): """ # Take the list of protocols and join them together, prefixing them # with their lengths. - protostr = b"".join( + protostr = b''.join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) @@ -1386,18 +1461,13 @@ class Context(object): :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. + bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It should return + one of those bytestrings, the chosen protocol. """ 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 - ) + self._context, self._alpn_select_callback, _ffi.NULL) def _set_ocsp_callback(self, helper, data): """ @@ -1458,7 +1528,15 @@ class Context(object): self._set_ocsp_callback(helper, data) +ContextType = deprecated( + Context, __name__, + "ContextType has been deprecated, use Context instead", DeprecationWarning +) + + class Connection(object): + """ + """ _reverse_mapping = WeakValueDictionary() def __init__(self, context, socket=None): @@ -1482,18 +1560,19 @@ class Connection(object): self._context = context self._app_data = None + # References to strings used for Next Protocol Negotiation. OpenSSL's + # header files suggest that these might get copied at some point, but + # doesn't specify when, so we store them here to make sure they don't + # get freed before OpenSSL uses them. + self._npn_advertise_callback_args = None + self._npn_select_callback_args = 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 - self._reverse_mapping[self._ssl] = self if socket is None: @@ -1511,8 +1590,7 @@ class Connection(object): self._from_ssl = None self._socket = socket set_result = _lib.SSL_set_fd( - self._ssl, _asFileDescriptor(self._socket) - ) + self._ssl, _asFileDescriptor(self._socket)) _openssl_assert(set_result == 1) def __getattr__(self, name): @@ -1521,16 +1599,19 @@ class Connection(object): on the Connection object. """ if self._socket is None: - raise AttributeError( - "'%s' object has no attribute '%s'" - % (self.__class__.__name__, name) - ) + raise AttributeError("'%s' object has no attribute '%s'" % ( + self.__class__.__name__, name + )) else: return getattr(self._socket, name) def _raise_ssl_error(self, ssl, result): if self._context._verify_helper is not None: self._context._verify_helper.raise_if_problem() + if self._context._npn_advertise_helper is not None: + self._context._npn_advertise_helper.raise_if_problem() + if self._context._npn_select_helper is not None: + self._context._npn_select_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: @@ -1585,6 +1666,7 @@ class Connection(object): _lib.SSL_set_SSL_CTX(self._ssl, context._context) self._context = context + @_requires_sni def get_servername(self): """ Retrieve the servername extension value if provided in the client hello @@ -1602,6 +1684,7 @@ class Connection(object): return _ffi.string(name) + @_requires_sni def set_tlsext_host_name(self, name): """ Set the value of the servername extension to send in the client hello. @@ -1641,18 +1724,18 @@ class Connection(object): # Backward compatibility buf = _text_to_bytes_and_warn("buf", buf) - with _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 + if isinstance(buf, memoryview): + buf = buf.tobytes() + if isinstance(buf, _buffer): + buf = str(buf) + if not isinstance(buf, bytes): + raise TypeError("data must be a memoryview, buffer or byte string") + if len(buf) > 2147483647: + raise ValueError("Cannot send more than 2**31-1 bytes at once.") + result = _lib.SSL_write(self._ssl, buf, len(buf)) + self._raise_ssl_error(self._ssl, result) + return result write = send def sendall(self, buf, flags=0): @@ -1668,22 +1751,28 @@ class Connection(object): """ buf = _text_to_bytes_and_warn("buf", buf) - with _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 + if isinstance(buf, memoryview): + buf = buf.tobytes() + if isinstance(buf, _buffer): + buf = str(buf) + if not isinstance(buf, bytes): + raise TypeError("buf must be a memoryview, buffer or byte string") + + left_to_send = len(buf) + total_sent = 0 + data = _ffi.new("char[]", buf) + + 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 def recv(self, bufsiz, flags=None): """ @@ -1701,7 +1790,6 @@ class Connection(object): 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, nbytes=None, flags=None): @@ -1798,11 +1886,10 @@ class Connection(object): if self._into_ssl is None: raise TypeError("Connection sock was not None") - with _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 + result = _lib.BIO_write(self._into_ssl, buf, len(buf)) + if result <= 0: + self._handle_bio_errors(self._into_ssl, result) + return result def renegotiate(self): """ @@ -1819,7 +1906,7 @@ class Connection(object): def do_handshake(self): """ Perform an SSL handshake (usually called after :meth:`renegotiate` or - one of :meth:`set_accept_state` or :meth:`set_connect_state`). This can + one of :meth:`set_accept_state` or :meth:`set_accept_state`). This can raise the same exceptions as :meth:`send` and :meth:`recv`. :return: None. @@ -1968,8 +2055,7 @@ class Connection(object): :raise: NotImplementedError """ raise NotImplementedError( - "Cannot make file object of OpenSSL.SSL.Connection" - ) + "Cannot make file object of OpenSSL.SSL.Connection") def get_app_data(self): """ @@ -2028,7 +2114,7 @@ class Connection(object): if session == _ffi.NULL: return None length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) - _openssl_assert(length > 0) + assert length > 0 outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_server_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2044,7 +2130,7 @@ class Connection(object): return None length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) - _openssl_assert(length > 0) + assert length > 0 outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_client_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2060,7 +2146,7 @@ class Connection(object): return None length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) - _openssl_assert(length > 0) + assert length > 0 outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_SESSION_get_master_key(session, outp, length) return _ffi.buffer(outp, length)[:] @@ -2082,16 +2168,10 @@ class Connection(object): 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, - ) + 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)[:] @@ -2127,22 +2207,6 @@ class Connection(object): return X509._from_raw_x509_ptr(cert) return None - @staticmethod - def _cert_stack_to_list(cert_stack): - """ - 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): """ Retrieve the other side's certificate (if any) @@ -2154,26 +2218,13 @@ class Connection(object): if cert_stack == _ffi.NULL: return None - return self._cert_stack_to_list(cert_stack) - - def get_verified_chain(self): - """ - 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) + result = [] + for i in range(_lib.sk_X509_num(cert_stack)): + # TODO could incref instead of dup here + cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i)) + pycert = X509._from_raw_x509_ptr(cert) + result.append(pycert) + return result def want_read(self): """ @@ -2241,7 +2292,8 @@ class Connection(object): raise TypeError("session must be a Session instance") result = _lib.SSL_set_session(self._ssl, session._session) - _openssl_assert(result == 1) + if not result: + _raise_current_error() def _get_finished_message(self, function): """ @@ -2374,6 +2426,23 @@ class Connection(object): version = _lib.SSL_version(self._ssl) return version + @_requires_npn + def get_next_proto_negotiated(self): + """ + Get the protocol that was negotiated by NPN. + + :returns: A bytestring of the protocol name. If no protocol has been + negotiated yet, returns an empty string. + + .. versionadded:: 0.15 + """ + data = _ffi.new("unsigned char **") + data_len = _ffi.new("unsigned int *") + + _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len) + + return _ffi.buffer(data[0], data_len[0])[:] + @_requires_alpn def set_alpn_protos(self, protos): """ @@ -2387,7 +2456,7 @@ class Connection(object): """ # Take the list of protocols and join them together, prefixing them # with their lengths. - protostr = b"".join( + protostr = b''.join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) @@ -2410,7 +2479,7 @@ class Connection(object): _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) if not data_len: - return b"" + return b'' return _ffi.buffer(data[0], data_len[0])[:] @@ -2427,6 +2496,12 @@ class Connection(object): _openssl_assert(rc == 1) +ConnectionType = deprecated( + Connection, __name__, + "ConnectionType has been deprecated, use Connection instead", + DeprecationWarning +) + # This is similar to the initialization calls at the end of OpenSSL/crypto.py # but is exercised mostly by the Context initializer. _lib.SSL_library_init() diff --git a/src/OpenSSL/__init__.py b/src/OpenSSL/__init__.py index 11e896a..810d00d 100644 --- a/src/OpenSSL/__init__.py +++ b/src/OpenSSL/__init__.py @@ -7,26 +7,14 @@ pyOpenSSL - A simple wrapper around the OpenSSL library from OpenSSL import crypto, SSL from OpenSSL.version import ( - __author__, - __copyright__, - __email__, - __license__, - __summary__, - __title__, - __uri__, - __version__, + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__, ) __all__ = [ - "SSL", - "crypto", - "__author__", - "__copyright__", - "__email__", - "__license__", - "__summary__", - "__title__", - "__uri__", - "__version__", + "SSL", "crypto", + + "__author__", "__copyright__", "__email__", "__license__", "__summary__", + "__title__", "__uri__", "__version__", ] diff --git a/src/OpenSSL/_util.py b/src/OpenSSL/_util.py index d04244c..cf8b888 100644 --- a/src/OpenSSL/_util.py +++ b/src/OpenSSL/_util.py @@ -1,7 +1,7 @@ import sys import warnings -from six import PY2, text_type +from six import PY3, binary_type, text_type from cryptography.hazmat.bindings.openssl.binding import Binding @@ -46,13 +46,10 @@ def exception_from_error_queue(exception_type): 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)), - ) - ) + 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) @@ -62,7 +59,6 @@ def make_assert(error): Create an assert function that uses :func:`exception_from_error_queue` to raise an exception wrapped by *error*. """ - def openssl_assert(ok): """ If *ok* is not True, retrieve the error from OpenSSL and raise it. @@ -83,14 +79,14 @@ def native(s): :raise TypeError: The input is neither :py:class:`bytes` nor :py:class:`unicode`. """ - if not isinstance(s, (bytes, text_type)): + if not isinstance(s, (binary_type, text_type)): raise TypeError("%r is neither bytes nor unicode" % s) - if PY2: + if PY3: + if isinstance(s, binary_type): + return s.decode("utf-8") + else: if isinstance(s, text_type): return s.encode("utf-8") - else: - if isinstance(s, bytes): - return s.decode("utf-8") return s @@ -103,7 +99,7 @@ def path_string(s): :return: An instance of :py:class:`bytes`. """ - if isinstance(s, bytes): + if isinstance(s, binary_type): return s elif isinstance(s, text_type): return s.encode(sys.getfilesystemencoding()) @@ -111,16 +107,12 @@ def path_string(s): raise TypeError("Path must be represented as bytes or unicode string") -if PY2: - +if PY3: def byte_string(s): - return s - - + return s.encode("charmap") else: - def byte_string(s): - return s.encode("charmap") + return s # A marker object to observe whether some optional arguments are passed any @@ -149,10 +141,7 @@ def text_to_bytes_and_warn(label, obj): warnings.warn( _TEXT_WARNING.format(label), category=DeprecationWarning, - stacklevel=3, + stacklevel=3 ) - return obj.encode("utf-8") + return obj.encode('utf-8') return obj - - -from_buffer = ffi.from_buffer diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py index 4265525..12e92df 100644 --- a/src/OpenSSL/crypto.py +++ b/src/OpenSSL/crypto.py @@ -1,4 +1,3 @@ -import calendar import datetime from base64 import b16encode @@ -8,11 +7,11 @@ from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__ from six import ( integer_types as _integer_types, text_type as _text_type, - PY2 as _PY2, -) + PY3 as _PY3) -from cryptography import utils, x509 +from cryptography import x509 from cryptography.hazmat.primitives.asymmetric import dsa, rsa +from cryptography.utils import deprecated from OpenSSL._util import ( ffi as _ffi, @@ -20,49 +19,48 @@ from OpenSSL._util import ( exception_from_error_queue as _exception_from_error_queue, byte_string as _byte_string, native as _native, - path_string as _path_string, UNSPECIFIED as _UNSPECIFIED, text_to_bytes_and_warn as _text_to_bytes_and_warn, make_assert as _make_assert, ) __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", - "PKCS7", - "PKCS12", - "NetscapeSPKI", - "load_publickey", - "load_privatekey", - "dump_certificate_request", - "load_certificate_request", - "sign", - "verify", - "dump_crl", - "load_crl", - "load_pkcs7_data", - "load_pkcs12", + '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', + 'PKCS7', + 'PKCS12', + 'NetscapeSPKI', + 'load_publickey', + 'load_privatekey', + 'dump_certificate_request', + 'load_certificate_request', + 'sign', + 'verify', + 'dump_crl', + 'load_crl', + 'load_pkcs7_data', + 'load_pkcs12' ] FILETYPE_PEM = _lib.SSL_FILETYPE_PEM @@ -96,7 +94,6 @@ def _get_backend(): triggering this side effect unless _get_backend is called. """ from cryptography.hazmat.backends.openssl.backend import backend - return backend @@ -139,7 +136,7 @@ def _bio_to_string(bio): """ Copy the contents of an OpenSSL BIO object into a Python byte string. """ - result_buffer = _ffi.new("char**") + result_buffer = _ffi.new('char**') buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) return _ffi.buffer(result_buffer[0], buffer_length)[:] @@ -176,7 +173,7 @@ def _get_asn1_time(timestamp): @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) + string_timestamp = _ffi.cast('ASN1_STRING*', timestamp) if _lib.ASN1_STRING_length(string_timestamp) == 0: return None elif ( @@ -199,8 +196,7 @@ def _get_asn1_time(timestamp): _untested_error("ASN1_TIME_to_generalizedtime") else: string_timestamp = _ffi.cast( - "ASN1_STRING*", generalized_timestamp[0] - ) + "ASN1_STRING*", generalized_timestamp[0]) string_data = _lib.ASN1_STRING_data(string_timestamp) string_result = _ffi.string(string_data) _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) @@ -224,7 +220,6 @@ class PKey(object): """ A class representing an DSA or RSA public key or key pair. """ - _only_public = False _initialized = True @@ -263,15 +258,8 @@ class PKey(object): .. versionadded:: 16.1.0 """ pkey = cls() - if not isinstance( - crypto_key, - ( - rsa.RSAPublicKey, - rsa.RSAPrivateKey, - dsa.DSAPublicKey, - dsa.DSAPrivateKey, - ), - ): + if not isinstance(crypto_key, (rsa.RSAPublicKey, rsa.RSAPrivateKey, + dsa.DSAPublicKey, dsa.DSAPrivateKey)): raise TypeError("Unsupported key type") pkey._pkey = crypto_key._evp_pkey @@ -358,7 +346,7 @@ class PKey(object): rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) rsa = _ffi.gc(rsa, _lib.RSA_free) result = _lib.RSA_check_key(rsa) - if result == 1: + if result: return True _raise_current_error() @@ -379,6 +367,13 @@ class PKey(object): return _lib.EVP_PKEY_bits(self._pkey) +PKeyType = deprecated( + PKey, __name__, + "PKeyType has been deprecated, use PKey instead", + DeprecationWarning +) + + class _EllipticCurve(object): """ A representation of a supported elliptic curve. @@ -388,11 +383,10 @@ class _EllipticCurve(object): instances each of which represents one curve supported by the system. @type _curves: :py:type:`NoneType` or :py:type:`set` """ - _curves = None - if not _PY2: - # This only necessary on Python 3. Moreover, it is broken on Python 2. + if _PY3: + # This only necessary on Python 3. Morever, it is broken on Python 2. def __ne__(self, other): """ Implement cooperation with the right-hand side argument of ``!=``. @@ -415,12 +409,14 @@ class _EllipticCurve(object): 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) + 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) + return set( + cls.from_nid(lib, c.nid) + for c in builtin_curves) @classmethod def _get_elliptic_curves(cls, lib): @@ -553,16 +549,14 @@ class X509Name(object): self._name = _ffi.gc(name, _lib.X509_NAME_free) def __setattr__(self, name, value): - if name.startswith("_"): + if name.startswith('_'): return super(X509Name, self).__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( - "attribute name must be string, not '%.200s'" - % (type(value).__name__,) - ) + raise TypeError("attribute name must be string, not '%.200s'" % ( + type(value).__name__,)) nid = _lib.OBJ_txt2nid(_byte_string(name)) if nid == _lib.NID_undef: @@ -583,11 +577,10 @@ class X509Name(object): break if isinstance(value, _text_type): - value = value.encode("utf-8") + value = value.encode('utf-8') add_result = _lib.X509_NAME_add_entry_by_NID( - self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0 - ) + self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0) if not add_result: _raise_current_error() @@ -623,9 +616,9 @@ class X509Name(object): _openssl_assert(data_length >= 0) try: - result = _ffi.buffer(result_buffer[0], data_length)[:].decode( - "utf-8" - ) + result = _ffi.buffer( + result_buffer[0], data_length + )[:].decode('utf-8') finally: # XXX untested _lib.OPENSSL_free(result_buffer[0]) @@ -637,7 +630,6 @@ class X509Name(object): return NotImplemented result = _lib.X509_NAME_cmp(self._name, other._name) return op(result, 0) - return f __eq__ = _cmp(__eq__) @@ -655,13 +647,11 @@ class X509Name(object): """ result_buffer = _ffi.new("char[]", 512) format_result = _lib.X509_NAME_oneline( - self._name, result_buffer, len(result_buffer) - ) + self._name, result_buffer, len(result_buffer)) _openssl_assert(format_result != _ffi.NULL) return "<X509Name object '%s'>" % ( - _native(_ffi.string(result_buffer)), - ) + _native(_ffi.string(result_buffer)),) def hash(self): """ @@ -682,7 +672,7 @@ class X509Name(object): :return: The DER encoded form of this name. :rtype: :py:class:`bytes` """ - result_buffer = _ffi.new("unsigned char**") + result_buffer = _ffi.new('unsigned char**') encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) _openssl_assert(encode_result >= 0) @@ -709,14 +699,20 @@ class X509Name(object): # ffi.string does not handle strings containing NULL bytes # (which may have been generated by old, broken software) - value = _ffi.buffer( - _lib.ASN1_STRING_data(fval), _lib.ASN1_STRING_length(fval) - )[:] + value = _ffi.buffer(_lib.ASN1_STRING_data(fval), + _lib.ASN1_STRING_length(fval))[:] result.append((_ffi.string(name), value)) return result +X509NameType = deprecated( + X509Name, __name__, + "X509NameType has been deprecated, use X509Name instead", + DeprecationWarning +) + + class X509Extension(object): """ An X.509 v3 certificate extension. @@ -812,8 +808,7 @@ class X509Extension(object): parts.append(_native(_bio_to_string(bio))) else: value = _native( - _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:] - ) + _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:]) parts.append(label + ":" + value) return ", ".join(parts) @@ -863,12 +858,19 @@ class X509Extension(object): .. versionadded:: 0.12 """ octet_result = _lib.X509_EXTENSION_get_data(self._extension) - string_result = _ffi.cast("ASN1_STRING*", octet_result) + string_result = _ffi.cast('ASN1_STRING*', octet_result) char_result = _lib.ASN1_STRING_data(string_result) result_length = _lib.ASN1_STRING_length(string_result) return _ffi.buffer(char_result, result_length)[:] +X509ExtensionType = deprecated( + X509Extension, __name__, + "X509ExtensionType has been deprecated, use X509Extension instead", + DeprecationWarning +) + + class X509Req(object): """ An X.509 certificate signing requests. @@ -889,9 +891,8 @@ class X509Req(object): .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateSigningRequest, + _CertificateSigningRequest ) - backend = _get_backend() return _CertificateSigningRequest(backend, self._req) @@ -1017,20 +1018,9 @@ class X509Req(object): """ 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 = X509Extension.__new__(X509Extension) - extension = _lib.X509_EXTENSION_dup( - _lib.sk_X509_EXTENSION_value(native_exts_obj, i) - ) - ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) + ext._extension = _lib.sk_X509_EXTENSION_value(native_exts_obj, i) exts.append(ext) return exts @@ -1080,11 +1070,17 @@ class X509Req(object): return result +X509ReqType = deprecated( + X509Req, __name__, + "X509ReqType has been deprecated, use X509Req instead", + DeprecationWarning +) + + class X509(object): """ An X.509 certificate. """ - def __init__(self): x509 = _lib.X509_new() _openssl_assert(x509 != _ffi.NULL) @@ -1110,7 +1106,6 @@ class X509(object): .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import _Certificate - backend = _get_backend() return _Certificate(backend, self._x509) @@ -1252,16 +1247,12 @@ class X509(object): result_length[0] = len(result_buffer) digest_result = _lib.X509_digest( - self._x509, digest, result_buffer, result_length - ) + 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]) - ] - ) + return b":".join([ + b16encode(ch).upper() for ch + in _ffi.buffer(result_buffer, result_length[0])]) def subject_name_hash(self): """ @@ -1286,7 +1277,7 @@ class X509(object): hex_serial = hex(serial)[2:] if not isinstance(hex_serial, bytes): - hex_serial = hex_serial.encode("ascii") + hex_serial = hex_serial.encode('ascii') bignum_serial = _ffi.new("BIGNUM**") @@ -1297,8 +1288,7 @@ class X509(object): if bignum_serial[0] == _ffi.NULL: set_result = _lib.ASN1_INTEGER_set( - _lib.X509_get_serialNumber(self._x509), small_serial - ) + _lib.X509_get_serialNumber(self._x509), small_serial) if set_result: # TODO Not tested _raise_current_error() @@ -1343,7 +1333,7 @@ class X509(object): if not isinstance(amount, int): raise TypeError("amount must be an integer") - notAfter = _lib.X509_getm_notAfter(self._x509) + notAfter = _lib.X509_get_notAfter(self._x509) _lib.X509_gmtime_adj(notAfter, amount) def gmtime_adj_notBefore(self, amount): @@ -1356,7 +1346,7 @@ class X509(object): if not isinstance(amount, int): raise TypeError("amount must be an integer") - notBefore = _lib.X509_getm_notBefore(self._x509) + notBefore = _lib.X509_get_notBefore(self._x509) _lib.X509_gmtime_adj(notBefore, amount) def has_expired(self): @@ -1385,7 +1375,7 @@ class X509(object): :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ - return self._get_boundary_time(_lib.X509_getm_notBefore) + return self._get_boundary_time(_lib.X509_get_notBefore) def _set_boundary_time(self, which, when): return _set_asn1_time(which(self._x509), when) @@ -1401,7 +1391,7 @@ class X509(object): :param bytes when: A timestamp string. :return: ``None`` """ - return self._set_boundary_time(_lib.X509_getm_notBefore, when) + return self._set_boundary_time(_lib.X509_get_notBefore, when) def get_notAfter(self): """ @@ -1414,7 +1404,7 @@ class X509(object): :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ - return self._get_boundary_time(_lib.X509_getm_notAfter) + return self._get_boundary_time(_lib.X509_get_notAfter) def set_notAfter(self, when): """ @@ -1427,7 +1417,7 @@ class X509(object): :param bytes when: A timestamp string. :return: ``None`` """ - return self._set_boundary_time(_lib.X509_getm_notAfter, when) + return self._set_boundary_time(_lib.X509_get_notAfter, when) def _get_name(self, which): name = X509Name.__new__(X509Name) @@ -1553,6 +1543,13 @@ class X509(object): return ext +X509Type = deprecated( + X509, __name__, + "X509Type has been deprecated, use X509 instead", + DeprecationWarning +) + + class X509StoreFlags(object): """ Flags for X509 verification, used to change the behavior of @@ -1563,7 +1560,6 @@ class X509StoreFlags(object): .. _OpenSSL Verification Flags: https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_flags.html """ - CRL_CHECK = _lib.X509_V_FLAG_CRL_CHECK CRL_CHECK_ALL = _lib.X509_V_FLAG_CRL_CHECK_ALL IGNORE_CRITICAL = _lib.X509_V_FLAG_IGNORE_CRITICAL @@ -1614,8 +1610,16 @@ class X509Store(object): if not isinstance(cert, X509): raise TypeError() - res = _lib.X509_STORE_add_cert(self._store, cert._x509) - _openssl_assert(res == 1) + # As of OpenSSL 1.1.0i adding the same cert to the store more than + # once doesn't cause an error. Accordingly, this code now silences + # the error for OpenSSL < 1.1.0i as well. + if _lib.X509_STORE_add_cert(self._store, cert._x509) == 0: + code = _lib.ERR_peek_error() + err_reason = _lib.ERR_GET_REASON(code) + _openssl_assert( + err_reason == _lib.X509_R_CERT_ALREADY_IN_HASH_TABLE + ) + _lib.ERR_clear_error() def add_crl(self, crl): """ @@ -1676,57 +1680,15 @@ class X509Store(object): 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()) - ) + _lib.X509_VERIFY_PARAM_set_time(param, int(vfy_time.strftime('%s'))) _openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0) - def load_locations(self, cafile, capath=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_string(cafile) - - if capath is None: - capath = _ffi.NULL - else: - capath = _path_string(capath) - - load_result = _lib.X509_STORE_load_locations( - self._store, cafile, capath - ) - if not load_result: - _raise_current_error() +X509StoreType = deprecated( + X509Store, __name__, + "X509StoreType has been deprecated, use X509Store instead", + DeprecationWarning +) class X509StoreContextError(Exception): @@ -1756,54 +1718,21 @@ class X509StoreContext(object): collected. :ivar _store: See the ``store`` ``__init__`` parameter. :ivar _cert: See the ``certificate`` ``__init__`` parameter. - :ivar _chain: See the ``chain`` ``__init__`` parameter. :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, certificate, chain=None): + def __init__(self, store, certificate): store_ctx = _lib.X509_STORE_CTX_new() self._store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) self._store = store self._cert = certificate - self._chain = self._build_certificate_stack(chain) # Make the store context available for use after instantiating this # class by initializing it now. Per testing, subsequent calls to # :meth:`_init` have no adverse affect. self._init() - @staticmethod - def _build_certificate_stack(certificates): - def cleanup(s): - # 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 - def _init(self): """ Set up the store context for a subsequent verification operation. @@ -1812,7 +1741,7 @@ class X509StoreContext(object): :meth:`_cleanup` will leak memory. """ ret = _lib.X509_STORE_CTX_init( - self._store_ctx, self._store._store, self._cert._x509, self._chain + self._store_ctx, self._store._store, self._cert._x509, _ffi.NULL ) if ret <= 0: _raise_current_error() @@ -1836,13 +1765,8 @@ class X509StoreContext(object): errors = [ _lib.X509_STORE_CTX_get_error(self._store_ctx), _lib.X509_STORE_CTX_get_error_depth(self._store_ctx), - _native( - _ffi.string( - _lib.X509_verify_cert_error_string( - _lib.X509_STORE_CTX_get_error(self._store_ctx) - ) - ) - ), + _native(_ffi.string(_lib.X509_verify_cert_error_string( + _lib.X509_STORE_CTX_get_error(self._store_ctx)))), ] # A context error should always be associated with a certificate, so we # expect this call to never return :class:`None`. @@ -1884,45 +1808,6 @@ class X509StoreContext(object): if ret <= 0: raise self._exception_from_context() - def get_verified_chain(self): - """ - 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 - """ - # Always re-initialize the store context in case - # :meth:`verify_certificate` is called multiple times. - # - # :meth:`_init` is called in :meth:`__init__` so _cleanup is called - # before _init to ensure memory is not leaked. - self._cleanup() - self._init() - ret = _lib.X509_verify_cert(self._store_ctx) - if ret <= 0: - self._cleanup() - raise self._exception_from_context() - - # Note: X509_STORE_CTX_get1_chain returns a deep copy of the chain. - cert_stack = _lib.X509_STORE_CTX_get1_chain(self._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) - self._cleanup() - return result - def load_certificate(type, buffer): """ @@ -1945,7 +1830,8 @@ def load_certificate(type, buffer): elif type == FILETYPE_ASN1: x509 = _lib.d2i_X509_bio(bio, _ffi.NULL) else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") + raise ValueError( + "type argument must be FILETYPE_PEM or FILETYPE_ASN1") if x509 == _ffi.NULL: _raise_current_error() @@ -1974,10 +1860,9 @@ def dump_certificate(type, cert): else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) + "FILETYPE_TEXT") - _openssl_assert(result_code == 1) + assert result_code == 1 return _bio_to_string(bio) @@ -2031,8 +1916,7 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None): if passphrase is None: raise TypeError( "if a value is given for cipher " - "one must also be given for passphrase" - ) + "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") @@ -2042,14 +1926,8 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None): 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, - ) + 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) @@ -2057,13 +1935,15 @@ def dump_privatekey(type, pkey, cipher=None, passphrase=None): 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) + 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" - ) + "FILETYPE_TEXT") _openssl_assert(result_code != 0) @@ -2074,7 +1954,6 @@ class Revoked(object): """ 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 @@ -2114,8 +1993,7 @@ class Revoked(object): asn1_serial = _ffi.gc( _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), - _lib.ASN1_INTEGER_free, - ) + _lib.ASN1_INTEGER_free) _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) def get_serial(self): @@ -2166,7 +2044,7 @@ class Revoked(object): elif not isinstance(reason, bytes): raise TypeError("reason must be None or a byte string") else: - reason = reason.lower().replace(b" ", b"") + 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() @@ -2178,8 +2056,7 @@ class Revoked(object): self._delete_reason() add_result = _lib.X509_REVOKED_add1_ext_i2d( - self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0 - ) + self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0) _openssl_assert(add_result == 1) def get_reason(self): @@ -2261,9 +2138,8 @@ class CRL(object): .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateRevocationList, + _CertificateRevocationList ) - backend = _get_backend() return _CertificateRevocationList(backend, self._crl) @@ -2370,7 +2246,7 @@ class CRL(object): def set_nextUpdate(self, when): """ - Set when the CRL will next be updated. + Set when the CRL will next be udpated. The timestamp is formatted as an ASN.1 TIME:: @@ -2403,15 +2279,13 @@ class CRL(object): 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) - ) + 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, key, type=FILETYPE_PEM, days=100, digest=_UNSPECIFIED - ): + def export(self, cert, key, type=FILETYPE_PEM, days=100, + digest=_UNSPECIFIED): """ Export the CRL as a string. @@ -2421,7 +2295,7 @@ class CRL(object): :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"``). + ``b"sha2566"``). :rtype: bytes """ @@ -2464,6 +2338,13 @@ class CRL(object): return dump_crl(type, self) +CRLType = deprecated( + CRL, __name__, + "CRLType has been deprecated, use CRL instead", + DeprecationWarning +) + + class PKCS7(object): def type_is_signed(self): """ @@ -2508,6 +2389,13 @@ class PKCS7(object): return _ffi.string(string_type) +PKCS7Type = deprecated( + PKCS7, __name__, + "PKCS7Type has been deprecated, use PKCS7 instead", + DeprecationWarning +) + + class PKCS12(object): """ A PKCS #12 archive. @@ -2669,17 +2557,10 @@ class PKCS12(object): cert = self._cert._x509 pkcs12 = _lib.PKCS12_create( - passphrase, - friendlyname, - pkey, - cert, - cacerts, + passphrase, friendlyname, pkey, cert, cacerts, _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, - iter, - maciter, - 0, - ) + iter, maciter, 0) if pkcs12 == _ffi.NULL: _raise_current_error() pkcs12 = _ffi.gc(pkcs12, _lib.PKCS12_free) @@ -2689,6 +2570,13 @@ class PKCS12(object): return _bio_to_string(bio) +PKCS12Type = deprecated( + PKCS12, __name__, + "PKCS12Type has been deprecated, use PKCS12 instead", + DeprecationWarning +) + + class NetscapeSPKI(object): """ A Netscape SPKI object. @@ -2779,6 +2667,13 @@ class NetscapeSPKI(object): _openssl_assert(set_result == 1) +NetscapeSPKIType = deprecated( + NetscapeSPKI, __name__, + "NetscapeSPKIType has been deprecated, use NetscapeSPKI instead", + DeprecationWarning +) + + class _PassphraseHelper(object): def __init__(self, type, passphrase, more_args=False, truncate=False): if type != FILETYPE_PEM and passphrase is not None: @@ -2794,7 +2689,9 @@ class _PassphraseHelper(object): def callback(self): if self._passphrase is None: return _ffi.NULL - elif isinstance(self._passphrase, bytes) or callable(self._passphrase): + elif isinstance(self._passphrase, bytes): + return _ffi.NULL + elif callable(self._passphrase): return _ffi.callback("pem_password_cb", self._read_passphrase) else: raise TypeError( @@ -2805,7 +2702,9 @@ class _PassphraseHelper(object): def callback_args(self): if self._passphrase is None: return _ffi.NULL - elif isinstance(self._passphrase, bytes) or callable(self._passphrase): + elif isinstance(self._passphrase, bytes): + return self._passphrase + elif callable(self._passphrase): return _ffi.NULL else: raise TypeError( @@ -2825,15 +2724,12 @@ class _PassphraseHelper(object): def _read_passphrase(self, buf, size, rwflag, userdata): try: - if callable(self._passphrase): - if self._more_args: - result = self._passphrase(size, rwflag, userdata) - else: - result = self._passphrase(rwflag) + if self._more_args: + result = self._passphrase(size, rwflag, userdata) else: - result = self._passphrase + result = self._passphrase(rwflag) if not isinstance(result, bytes): - raise ValueError("Bytes expected") + raise ValueError("String expected") if len(result) > size: if self._truncate: result = result[:size] @@ -2842,7 +2738,7 @@ class _PassphraseHelper(object): "passphrase returned by callback is too long" ) for i in range(len(result)): - buf[i] = result[i : i + 1] + buf[i] = result[i:i + 1] return len(result) except Exception as e: self._problems.append(e) @@ -2867,8 +2763,7 @@ def load_publickey(type, buffer): if type == FILETYPE_PEM: evp_pkey = _lib.PEM_read_bio_PUBKEY( - bio, _ffi.NULL, _ffi.NULL, _ffi.NULL - ) + bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: evp_pkey = _lib.d2i_PUBKEY_bio(bio, _ffi.NULL) else: @@ -2904,8 +2799,7 @@ def load_privatekey(type, buffer, passphrase=None): helper = _PassphraseHelper(type, passphrase) if type == FILETYPE_PEM: evp_pkey = _lib.PEM_read_bio_PrivateKey( - bio, _ffi.NULL, helper.callback, helper.callback_args - ) + 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) @@ -3004,8 +2898,7 @@ def sign(pkey, data, digest): 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 - ) + md_ctx, signature_buffer, signature_length, pkey._pkey) _openssl_assert(final_result == 1) return _ffi.buffer(signature_buffer, signature_length[0])[:] @@ -3069,10 +2962,9 @@ def dump_crl(type, crl): else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT" - ) + "FILETYPE_TEXT") - _openssl_assert(ret == 1) + assert ret == 1 return _bio_to_string(bio) @@ -3135,17 +3027,6 @@ def load_pkcs7_data(type, buffer): return pypkcs7 -load_pkcs7_data = utils.deprecated( - load_pkcs7_data, - __name__, - ( - "PKCS#7 support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, -) - - def load_pkcs12(buffer, passphrase=None): """ Load pkcs12 data from the string *buffer*. If the pkcs12 structure is @@ -3233,17 +3114,6 @@ def load_pkcs12(buffer, passphrase=None): return pkcs12 -load_pkcs12 = utils.deprecated( - load_pkcs12, - __name__, - ( - "PKCS#12 support in pyOpenSSL is deprecated. You should use the APIs " - "in cryptography." - ), - DeprecationWarning, -) - - # There are no direct unit tests for this initialization. It is tested # indirectly since it is necessary for functions like dump_privatekey when # using encryption. @@ -3262,4 +3132,4 @@ _lib.SSL_load_error_strings() # Set the default string mask to match OpenSSL upstream (since 2005) and # RFC5280 recommendations. -_lib.ASN1_STRING_set_default_mask_asc(b"utf8only") +_lib.ASN1_STRING_set_default_mask_asc(b'utf8only') diff --git a/src/OpenSSL/debug.py b/src/OpenSSL/debug.py index 04521d5..0d37bf5 100644 --- a/src/OpenSSL/debug.py +++ b/src/OpenSSL/debug.py @@ -16,7 +16,7 @@ cryptography: {cryptography} cffi: {cffi} cryptography's compiled against OpenSSL: {crypto_openssl_compile} cryptography's linked OpenSSL: {crypto_openssl_link} -Python's OpenSSL: {python_openssl} +Pythons's OpenSSL: {python_openssl} Python executable: {python} Python version: {python_version} Platform: {platform} diff --git a/src/OpenSSL/tsafe.py b/src/OpenSSL/tsafe.py new file mode 100644 index 0000000..f1c6f67 --- /dev/null +++ b/src/OpenSSL/tsafe.py @@ -0,0 +1,31 @@ +import warnings +from threading import RLock as _RLock + +from OpenSSL import SSL as _ssl + + +warnings.warn( + "OpenSSL.tsafe is deprecated and will be removed", + DeprecationWarning, stacklevel=3 +) + + +class Connection: + def __init__(self, *args): + self._ssl_conn = _ssl.Connection(*args) + self._lock = _RLock() + + for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', + 'renegotiate', 'bind', 'listen', 'connect', 'accept', + 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', + 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', + 'makefile', 'get_app_data', 'set_app_data', 'state_string', + 'sock_shutdown', 'get_peer_certificate', 'get_peer_cert_chain', + 'want_read', 'want_write', 'set_connect_state', + 'set_accept_state', 'connect_ex', 'sendall'): + exec("""def %s(self, *args): + self._lock.acquire() + try: + return self._ssl_conn.%s(*args) + finally: + self._lock.release()\n""" % (f, f)) diff --git a/src/OpenSSL/version.py b/src/OpenSSL/version.py index 9348867..40f31c3 100644 --- a/src/OpenSSL/version.py +++ b/src/OpenSSL/version.py @@ -7,17 +7,11 @@ pyOpenSSL - A simple wrapper around the OpenSSL library """ __all__ = [ - "__author__", - "__copyright__", - "__email__", - "__license__", - "__summary__", - "__title__", - "__uri__", - "__version__", + "__author__", "__copyright__", "__email__", "__license__", "__summary__", + "__title__", "__uri__", "__version__", ] -__version__ = "20.0.1" +__version__ = "19.0.0" __title__ = "pyOpenSSL" __uri__ = "https://pyopenssl.org/" @@ -25,4 +19,4 @@ __summary__ = "Python wrapper module around the OpenSSL library" __author__ = "The pyOpenSSL developers" __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -__copyright__ = "Copyright 2001-2020 {0}".format(__author__) +__copyright__ = "Copyright 2001-2017 {0}".format(__author__) diff --git a/tests/conftest.py b/tests/conftest.py index 5bae6b8..366624e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,7 +12,7 @@ def pytest_report_header(config): return "OpenSSL: {openssl}\ncryptography: {cryptography}".format( openssl=OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION), - cryptography=cryptography.__version__, + cryptography=cryptography.__version__ ) diff --git a/tests/memdbg.py b/tests/memdbg.py index 590b72d..6e608a7 100644 --- a/tests/memdbg.py +++ b/tests/memdbg.py @@ -5,8 +5,8 @@ import traceback from cffi import api as _api -sys.modules["ssl"] = None -sys.modules["_hashlib"] = None +sys.modules['ssl'] = None +sys.modules['_hashlib'] = None _ffi = _api.FFI() @@ -16,22 +16,18 @@ _ffi.cdef( 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 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); - """ -) # noqa + """) # noqa _api = _ffi.verify( """ #include <openssl/crypto.h> #include <stdlib.h> #include <execinfo.h> - """, - libraries=["crypto"], -) + """, libraries=["crypto"]) C = _ffi.dlopen(None) verbose = False @@ -84,8 +80,8 @@ def free(p): if _api.CRYPTO_set_mem_functions(malloc, realloc, free): - log("Enabled memory debugging") + log('Enabled memory debugging') heap = {} else: - log("Failed to enable memory debugging") + log('Failed to enable memory debugging') heap = None diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 265d31e..c938021 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -10,10 +10,11 @@ from warnings import simplefilter import base64 from subprocess import PIPE, Popen from datetime import datetime, timedelta -import sys import pytest +from six import binary_type + from cryptography import x509 from cryptography.hazmat.backends.openssl.backend import backend from cryptography.hazmat.primitives import serialization @@ -21,40 +22,30 @@ from cryptography.hazmat.primitives.asymmetric import rsa import flaky -from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey -from OpenSSL.crypto import X509, X509Name +from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType +from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType from OpenSSL.crypto import ( X509Store, X509StoreFlags, + X509StoreType, X509StoreContext, - X509StoreContextError, + X509StoreContextError ) -from OpenSSL.crypto import X509Req -from OpenSSL.crypto import X509Extension +from OpenSSL.crypto import X509Req, X509ReqType +from OpenSSL.crypto import X509Extension, X509ExtensionType from OpenSSL.crypto import load_certificate, load_privatekey from OpenSSL.crypto import load_publickey, dump_publickey from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT from OpenSSL.crypto import dump_certificate, load_certificate_request from OpenSSL.crypto import dump_certificate_request, dump_privatekey -from OpenSSL.crypto import PKCS7, load_pkcs7_data -from OpenSSL.crypto import PKCS12, load_pkcs12 +from OpenSSL.crypto import PKCS7, PKCS7Type, load_pkcs7_data +from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12 from OpenSSL.crypto import CRL, Revoked, dump_crl, load_crl -from OpenSSL.crypto import NetscapeSPKI +from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType from OpenSSL.crypto import ( - sign, - verify, - get_elliptic_curve, - get_elliptic_curves, -) + sign, verify, get_elliptic_curve, get_elliptic_curves) -from OpenSSL._util import ffi as _ffi, lib as _lib - -from .util import ( - EqualityTestsMixin, - is_consistent_type, - WARNING_TYPE_EXPECTED, - NON_ASCII, -) +from .util import EqualityTestsMixin, is_consistent_type, WARNING_TYPE_EXPECTED def normalize_privatekey_pem(pem): @@ -88,398 +79,213 @@ w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn """ root_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIE7jCCA1agAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE +MIIC6TCCAlKgAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU -ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAwODAyMTcxMTE5 -WhcNNDcxMjIwMTcxMTE5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO +ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTcwNjExMjIzMjU5 +WhcNMzcwNjA2MjIzMjU5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO 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/ +bmcgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA+ZpC6Yu6ukTn +bu5IQd0vWmpwNGZbO773xjpgfNP8nspYRqbIwI1np9FbUkJHvzZRDxrTt/LbFewr +LhZ0prHIbwJxq3CZe+m9FDjh1IA0yKEcQukA1N3JWnoMLKwQPrCRAW6seUXV2yER +onDxv/KkOGZtUijoKLXG8ImqK9ssWdsCAwEAAaOBuzCBuDAdBgNVHQ4EFgQUg1V3 +LV4h8UkMCSTnVAkSjch+BK4wgYgGA1UdIwSBgDB+gBSDVXctXiHxSQwJJOdUCRKN +yH4ErqFcpFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdD +aGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3Qg +Q0GCCD0MxODG3rn0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEANFYQ +R+T70VcZ+SnvURnwviFgCXeedBzCr21meo+DNHbkp2gudB9W8Xrned/wtUBVymy9 +gjB5jINfU7Lci0H57Evsw96UJJVfhXdUMHpqt1RGCoEd9FWnrDyrSy0NysnBT2bH +lEqxh3aFEUx9IOQ4sgnx1/NOFXBpkRtivl6O0Ec= -----END CERTIFICATE----- """ root_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- """ -root_key_der = base64.b64decode( - """ -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' -""" -) - -normalized_root_key_pem = normalize_privatekey_pem(root_key_pem) - intermediate_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIEXDCCAsSgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQELBQAw +MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw -DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAw -ODAyMTcxMTIwWhcNNDcxMjIwMTcxMTIwWjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh +DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw +ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh 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== +MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK +FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT +21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID +AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX +QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+ +9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF +9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT -----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= +MIICWwIBAAKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmKFGIb +ljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT21H2 +qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwIDAQAB +AoGAfSZVV80pSeOKHTYfbGdNY/jHdU9eFUa/33YWriXU+77EhpIItJjkRRgivIfo +rhFJpBSGmDLblaqepm8emsXMeH4+2QzOYIf0QGGP6E6scjTt1PLqdqKfVJ1a2REN +147cujNcmFJb/5VQHHMpaPTgttEjlzuww4+BCDPsVRABWrkCQQD3loH36nLoQTtf ++kQq0T6Bs9/UWkTAGo0ND81ALj0F8Ie1oeZg6RNT96RxZ3aVuFTESTv6/TbjWywO +wdzlmV1vAkEA38rTJ6PTwaJlw5OttdDzAXGPB9tDmzh9oSi7cHwQQXizYd8MBYx4 +sjHUKD3dCQnb1dxJFhd3BT5HsnkRMbVZXQJAbXduH17ZTzcIOXc9jHDXYiFVZV5D +52vV0WCbLzVCZc3jMrtSUKa8lPN5EWrdU3UchWybyG0MR5mX8S5lrF4SoQJAIyUD +DBKaSqpqONCUUx1BTFS9FYrFjzbL4+c1qHCTTPTblt8kUCrDOZjBrKAqeiTmNSum +/qUot9YUBF8m6BuGsQJATHHmdFy/fG1VLkyBp49CAa8tN3Z5r/CgTznI4DfMTf4C +NbRHn2UmYlwQBa+L5lg9phewNe8aEwpPyPLoV85U8Q== -----END RSA PRIVATE KEY----- """ server_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIEKTCCApGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV +MIICJDCCAY2gAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV 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== +VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTE3MDYxMjAwMTA1 +N1oXDTM3MDYwNzAwMTA1N1owGDEWMBQGA1UEAwwNbG92ZWx5IHNlcnZlcjCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvqb4brndXS2kEL84qXbZXE6LYK+UrhNi +70sdIM/24NVN7tXkPlOXqrMWhFHHml+aeSpPkH5b1vKnY1TcULmEubnNICtvjmZ5 +SGMQn+J+RmBs1SMd0EgY/0wBBQdlrlYp2QYgm8YC+zxTNSqWvhMFZAgHbj6Un5SS +T8JGBqytjB0CAwEAAaM2MDQwHQYDVR0OBBYEFINVdy1eIfFJDAkk51QJEo3IfgSu +MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4GBAGki1K6WgHHJ +qC6aY2EowjaWOXLO6jUZIhGk7BA7vMRfNug429AOZ4m5F6OQhzmJmlw67Jyu2FeI +h0VtBuQoHPtjqZXF59oX6hMMmGLMs9pV0UA3fJs5MYA4/V5ZcQy0Ie0QoJNejLzE +6V1Qz1rRTYLUyEcpI7ZCmBg2KQQI8YZI -----END CERTIFICATE----- """ -server_key_pem = normalize_privatekey_pem( - b"""-----BEGIN RSA PRIVATE KEY----- -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 +server_key_pem = normalize_privatekey_pem(b"""-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+ +U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q +SBj/TAEFB2WuVinZBiCbxgL7PFM1Kpa+EwVkCAduPpSflJJPwkYGrK2MHQIDAQAB +AoGAbwuZ0AR6JveahBaczjfnSpiFHf+mve2UxoQdpyr6ROJ4zg/PLW5K/KXrC48G +j6f3tXMrfKHcpEoZrQWUfYBRCUsGD5DCazEhD8zlxEHahIsqpwA0WWssJA2VOLEN +j6DuV2pCFbw67rfTBkTSo32ahfXxEKev5KswZk0JIzH3ooECQQDgzS9AI89h0gs8 +Dt+1m11Rzqo3vZML7ZIyGApUzVan+a7hbc33nbGRkAXjHaUBJO31it/H6dTO+uwX +msWwNG5ZAkEA2RyFKs5xR5USTFaKLWCgpH/ydV96KPOpBND7TKQx62snDenFNNbn +FwwOhpahld+vqhYk+pfuWWUpQciE+Bu7ZQJASjfT4sQv4qbbKK/scePicnDdx9th +4e1EeB9xwb+tXXXUo/6Bor/AcUNwfiQ6Zt9PZOK9sR3lMZSsP7rMi7kzuQJABie6 +1sXXjFH7nNJvRG4S39cIxq8YRYTy68II/dlB2QzGpKxV/POCxbJ/zu0CU79tuYK7 +NaeNCFfH3aeTrX0LyQJAMBWjWmeKM2G2sCExheeQK0ROnaBC8itCECD4Jsve4nqf +r50+LF74iLXFwqysVCebPKMOpDWp/qQ1BbJQIPs7/A== -----END RSA PRIVATE KEY----- -""" -) +""") intermediate_server_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIEXTCCAsWgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQELBQAw +MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh -biBEaWVnbzAeFw0yMDA4MDIxNzExMjBaFw00NzEyMjAxNzExMjBaMG4xHTAbBgNV +biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV 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= +biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1 +iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4 ++kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm +biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw +UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN +3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6 +x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA== -----END CERTIFICATE----- """ intermediate_server_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- """ client_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIEJzCCAo+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV +MIICIjCCAYugAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV 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= +VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTE3MDYxMjAwMDQx +M1oXDTM3MDYwNzAwMDQxM1owFjEUMBIGA1UEAwwLdWdseSBjbGllbnQwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAMBmH9JG02bme0xPipvpjMSlOugyWrauf4at +EdGJn7GQLD8IY2Fu0+Kvv9DFpSPboFKZCsfDVsYoRs+xaJbtt1dJ6ymX7EqKS7gb +8q+eeZZ14keqyJd5Rm2q6swQtw9ADD3E8cS6GqpQm+8SgxOycISoYz7sO1ugJFqN +jId+W4BFAgMBAAGjNjA0MB0GA1UdDgQWBBSDVXctXiHxSQwJJOdUCRKNyH4ErjAT +BgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOBgQAMqcHyweaCOZNN +dWQQOsBKQlL5wqVVZwucHPWqobjxpULKy9gS2ha2zbgkXcB/BnBOSwe0Fm+MJV0T +NbnTghcGJNpEH7VKn4xSLvIGZmnZZWgxeIB16z4GhpkK2fShBJ+6GKZjsgjT0lSH +JRgjHbWutZfZvbSHXr9n7PIphG1Ojg== -----END CERTIFICATE----- """ -client_key_pem = normalize_privatekey_pem( - b"""-----BEGIN RSA PRIVATE KEY----- -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 +client_key_pem = normalize_privatekey_pem(b"""-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2rn+GrRHRiZ+xkCw/CGNh +btPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xKiku4G/KvnnmWdeJHqsiX +eUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7DtboCRajYyHfluARQIDAQAB +AoGATkZ+NceY5Glqyl4mD06SdcKfV65814vg2EL7V9t8+/mi9rYL8KztSXGlQWPX +zuHgtRoMl78yQ4ZJYOBVo+nsx8KZNRCEBlE19bamSbQLCeQMenWnpeYyQUZ908gF +h6L9qsFVJepgA9RDgAjyDoS5CaWCdCCPCH2lDkdcqC54SVUCQQDseuduc4wi8h4t +V8AahUn9fn9gYfhoNuM0gdguTA0nPLVWz4hy1yJiWYQe0H7NLNNTmCKiLQaJpAbb +TC6vE8C7AkEA0Ee8CMJUc20BnGEmxwgWcVuqFWaKCo8jTH1X38FlATUsyR3krjW2 +dL3yDD9NwHxsYP7nTKp/U8MV7U9IBn4y/wJBAJl7H0/BcLeRmuJk7IqJ7b635iYB +D/9beFUw3MUXmQXZUfyYz39xf6CDZsu1GEdEC5haykeln3Of4M9d/4Kj+FcCQQCY +si6xwT7GzMDkk/ko684AV3KPc/h6G0yGtFIrMg7J3uExpR/VdH2KgwMkZXisSMvw +JJEQjOMCVsEJlRk54WWjAkEAzoZNH6UhDdBK5F38rVt/y4SEHgbSfJHIAmPS32Kq +f6GGcfNpip0Uk7q7udTKuX7Q/buZi/C4YW7u3VKAquv9NA== -----END RSA PRIVATE KEY----- +""") + +cleartextCertificatePEM = b"""-----BEGIN CERTIFICATE----- +MIIC6TCCAlKgAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE +BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU +ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTcwNjExMjIzMjU5 +WhcNMzcwNjA2MjIzMjU5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO +BgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMTD1Rlc3Rp +bmcgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA+ZpC6Yu6ukTn +bu5IQd0vWmpwNGZbO773xjpgfNP8nspYRqbIwI1np9FbUkJHvzZRDxrTt/LbFewr +LhZ0prHIbwJxq3CZe+m9FDjh1IA0yKEcQukA1N3JWnoMLKwQPrCRAW6seUXV2yER +onDxv/KkOGZtUijoKLXG8ImqK9ssWdsCAwEAAaOBuzCBuDAdBgNVHQ4EFgQUg1V3 +LV4h8UkMCSTnVAkSjch+BK4wgYgGA1UdIwSBgDB+gBSDVXctXiHxSQwJJOdUCRKN +yH4ErqFcpFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdD +aGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3Qg +Q0GCCD0MxODG3rn0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEANFYQ +R+T70VcZ+SnvURnwviFgCXeedBzCr21meo+DNHbkp2gudB9W8Xrned/wtUBVymy9 +gjB5jINfU7Lci0H57Evsw96UJJVfhXdUMHpqt1RGCoEd9FWnrDyrSy0NysnBT2bH +lEqxh3aFEUx9IOQ4sgnx1/NOFXBpkRtivl6O0Ec= +-----END CERTIFICATE----- """ -) + +cleartextPrivateKeyPEM = normalize_privatekey_pem(b"""\ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA +jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU +3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB +AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB +yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa +6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM +BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD +u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk +PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr +I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8 +ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3 +6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2 +cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq +-----END RSA PRIVATE KEY----- +""") cleartextCertificateRequestPEM = b"""-----BEGIN CERTIFICATE REQUEST----- MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH @@ -516,23 +322,6 @@ MbzjS007Oe4qqBnCWaFPSnJX6uLApeTbqAxAeyCql56ULW5x6vDMNC3dwjvS/CEh 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 @@ -573,8 +362,7 @@ Ho4EzbYCOaEAMQA= -----END PKCS7----- """ -pkcs7DataASN1 = base64.b64decode( - b""" +pkcs7DataASN1 = base64.b64decode(b""" MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN @@ -593,8 +381,7 @@ bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG /g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 Ho4EzbYCOaEAMQA= -""" -) +""") crlData = b"""\ -----BEGIN X509 CRL----- @@ -777,12 +564,6 @@ e3fJQJwX9+KsHRut6qNZDUbvRbtO1YIAwB4UJZjwAjEAtXCPURS5A4McZHnSwgTi Td8GMrwKz0557OxxtKN6uVVy4ACFMqEw0zN/KJI1vxc9 -----END CERTIFICATE-----""" -rsa_p_not_prime_pem = """ ------BEGIN RSA PRIVATE KEY----- -MBsCAQACAS0CAQcCAQACAQ8CAQMCAQACAQACAQA= ------END RSA PRIVATE KEY----- -""" - @pytest.fixture def x509_data(): @@ -822,50 +603,42 @@ class TestX509Ext(object): # 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" + str(X509Extension(b'basicConstraints', True, b'CA:false')) == + 'CA:FALSE' ) def test_type(self): """ - `X509Extension` can be used to create instances of that type. + `X509Extension` and `X509ExtensionType` refer to the same type object + and can be used to create instances of that type. """ + assert X509Extension is X509ExtensionType assert is_consistent_type( X509Extension, - "X509Extension", - b"basicConstraints", - True, - b"CA:true", - ) + '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", - ), - ], - ) + and an extension value and returns an `X509ExtensionType` instance. + """ + basic = X509Extension(b'basicConstraints', True, b'CA:true') + assert isinstance(basic, X509ExtensionType) + + comment = X509Extension(b'nsComment', False, b'pyOpenSSL unit test') + assert isinstance(comment, X509ExtensionType) + + @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 @@ -874,19 +647,19 @@ class TestX509Ext(object): with pytest.raises(Error): X509Extension(type_name, critical, value) - @pytest.mark.parametrize("critical_flag", [True, False]) + @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") + 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")], - ) + @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 @@ -900,9 +673,9 @@ class TestX509Ext(object): `X509Extension.get_data` returns a string giving the data of the extension. """ - ext = X509Extension(b"basicConstraints", True, b"CA:true") + 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" + assert ext.get_data() == b'0\x03\x01\x01\xff' def test_unused_subject(self, x509_data): """ @@ -911,14 +684,13 @@ class TestX509Ext(object): """ pkey, x509 = x509_data ext1 = X509Extension( - b"basicConstraints", False, b"CA:TRUE", subject=x509 - ) + b'basicConstraints', False, b'CA:TRUE', subject=x509) x509.add_extensions([ext1]) - x509.sign(pkey, "sha1") + x509.sign(pkey, 'sha1') # 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 + assert b'X509v3 Basic Constraints:' in text + assert b'CA:TRUE' in text def test_subject(self, x509_data): """ @@ -927,12 +699,11 @@ class TestX509Ext(object): """ pkey, x509 = x509_data ext3 = X509Extension( - b"subjectKeyIdentifier", False, b"hash", subject=x509 - ) + b'subjectKeyIdentifier', False, b'hash', subject=x509) x509.add_extensions([ext3]) - x509.sign(pkey, "sha1") + x509.sign(pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, x509) - assert b"X509v3 Subject Key Identifier:" in text + assert b'X509v3 Subject Key Identifier:' in text def test_missing_subject(self): """ @@ -940,9 +711,14 @@ class TestX509Ext(object): is given no value, something happens. """ with pytest.raises(Error): - X509Extension(b"subjectKeyIdentifier", False, b"hash") - - @pytest.mark.parametrize("bad_obj", [True, object(), "hello", []]) + 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 @@ -950,8 +726,7 @@ class TestX509Ext(object): """ with pytest.raises(TypeError): X509Extension( - "basicConstraints", False, "CA:TRUE", subject=bad_obj - ) + 'basicConstraints', False, 'CA:TRUE', subject=bad_obj) def test_unused_issuer(self, x509_data): """ @@ -960,13 +735,12 @@ class TestX509Ext(object): """ pkey, x509 = x509_data ext1 = X509Extension( - b"basicConstraints", False, b"CA:TRUE", issuer=x509 - ) + b'basicConstraints', False, b'CA:TRUE', issuer=x509) x509.add_extensions([ext1]) - x509.sign(pkey, "sha1") + x509.sign(pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, x509) - assert b"X509v3 Basic Constraints:" in text - assert b"CA:TRUE" in text + assert b'X509v3 Basic Constraints:' in text + assert b'CA:TRUE' in text def test_issuer(self, x509_data): """ @@ -975,13 +749,13 @@ class TestX509Ext(object): """ pkey, x509 = x509_data ext2 = X509Extension( - b"authorityKeyIdentifier", False, b"issuer:always", issuer=x509 - ) + b'authorityKeyIdentifier', False, b'issuer:always', + issuer=x509) x509.add_extensions([ext2]) - x509.sign(pkey, "sha1") + x509.sign(pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, x509) - assert b"X509v3 Authority Key Identifier:" in text - assert b"DirName:/CN=Yoda root CA" in text + assert b'X509v3 Authority Key Identifier:' in text + assert b'DirName:/CN=Yoda root CA' in text def test_missing_issuer(self): """ @@ -990,10 +764,15 @@ class TestX509Ext(object): """ with pytest.raises(Error): X509Extension( - b"authorityKeyIdentifier", False, b"keyid:always,issuer:always" - ) - - @pytest.mark.parametrize("bad_obj", [True, object(), "hello", []]) + 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 @@ -1001,11 +780,8 @@ class TestX509Ext(object): """ with pytest.raises(TypeError): X509Extension( - "basicConstraints", - False, - "keyid:always,issuer:always", - issuer=bad_obj, - ) + 'basicConstraints', False, 'keyid:always,issuer:always', + issuer=bad_obj) class TestPKey(object): @@ -1063,7 +839,7 @@ class TestPKey(object): """ PKey.to_cryptography_key creates a proper cryptography private key. """ - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) key = pkey.to_cryptography_key() assert isinstance(key, rsa.RSAPrivateKey) @@ -1071,9 +847,11 @@ class TestPKey(object): def test_type(self): """ - `PKey` can be used to create instances of that type. + `PKey` and `PKeyType` refer to the same type object and can be used to + create instances of that type. """ - assert is_consistent_type(PKey, "PKey") + assert PKey is PKeyType + assert is_consistent_type(PKey, 'PKey') def test_construction(self): """ @@ -1195,14 +973,6 @@ class TestPKey(object): with pytest.raises(TypeError): pub.check() - def test_check_pr_897(self): - """ - `PKey.check` raises `OpenSSL.crypto.Error` if provided with broken key - """ - pkey = load_privatekey(FILETYPE_PEM, rsa_p_not_prime_pem) - with pytest.raises(Error): - pkey.check() - def x509_name(**attrs): """ @@ -1215,7 +985,6 @@ def x509_name(**attrs): # 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) @@ -1229,10 +998,14 @@ class TestX509Name(object): def test_type(self): """ - The type of X509Name objects is `X509Name`. + The type of X509Name objects is `X509NameType`. """ + assert X509Name is X509NameType + assert X509NameType.__name__ == 'X509Name' + assert isinstance(X509NameType, type) + name = x509_name() - assert isinstance(name, X509Name) + assert isinstance(name, X509NameType) def test_only_string_attributes(self): """ @@ -1323,7 +1096,6 @@ class TestX509Name(object): """ `X509Name` instances should compare based on their NIDs. """ - def _equality(a, b, assert_true, assert_false): assert_true(a == b) assert_false(a != b) @@ -1347,28 +1119,30 @@ class TestX509Name(object): 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")) + 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")) + 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"), - ) + 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")) + 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(CN="foo"), + x509_name(OU="foo")) assert_not_equal(x509_name(), object()) @@ -1388,7 +1162,8 @@ class TestX509Name(object): # 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")) + assert_less_than(x509_name(CN="abc"), + x509_name(CN="def")) def assert_greater_than(a, b): _inequality(a, b, assert_false, assert_true) @@ -1396,7 +1171,8 @@ class TestX509Name(object): # 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")) + assert_greater_than(x509_name(CN="def"), + x509_name(CN="abc")) def test_hash(self): """ @@ -1413,10 +1189,9 @@ class TestX509Name(object): `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" - ) + 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): """ @@ -1447,8 +1222,8 @@ class TestX509Name(object): 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" + 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): """ @@ -1517,7 +1292,7 @@ class _PKeyInteractionTestsMixin: 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: + if getattr(request, 'verify', None) is not None: pub = request.get_pubkey() assert request.verify(pub) # Make another key that won't verify. @@ -1540,16 +1315,18 @@ class TestX509Req(_PKeyInteractionTestsMixin): def test_type(self): """ - `X509Req` can be used to create instances of that type. + `X509Req` and `X509ReqType` refer to the same type object and can be + used to create instances of that type. """ - assert is_consistent_type(X509Req, "X509Req") + assert X509Req is X509ReqType + assert is_consistent_type(X509Req, 'X509Req') def test_construction(self): """ - `X509Req` takes no arguments and returns an `X509Req` instance. + `X509Req` takes no arguments and returns an `X509ReqType` instance. """ request = X509Req() - assert isinstance(request, X509Req) + assert isinstance(request, X509ReqType) def test_version(self): """ @@ -1581,7 +1358,7 @@ class TestX509Req(_PKeyInteractionTestsMixin): """ request = X509Req() subject = request.get_subject() - assert isinstance(subject, X509Name) + assert isinstance(subject, X509NameType) subject.commonName = "foo" assert request.get_subject().commonName == "foo" del request @@ -1594,14 +1371,13 @@ class TestX509Req(_PKeyInteractionTestsMixin): and adds them to the X509 request. """ request = X509Req() - request.add_extensions( - [X509Extension(b"basicConstraints", True, b"CA:false")] - ) + 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_short_name() == b'basicConstraints' assert exts[0].get_critical() == 1 - assert exts[0].get_data() == b"0\x00" + assert exts[0].get_data() == b'0\x00' def test_get_extensions(self): """ @@ -1611,23 +1387,17 @@ class TestX509Req(_PKeyInteractionTestsMixin): request = X509Req() exts = request.get_extensions() assert exts == [] - request.add_extensions( - [ - X509Extension(b"basicConstraints", True, b"CA:true"), - X509Extension(b"keyUsage", False, b"digitalSignature"), - ] - ) + 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_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[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 + assert exts[1].get_data() == b'\x03\x02\x07\x80' def test_add_extensions_wrong_args(self): """ @@ -1667,7 +1437,7 @@ class TestX509Req(_PKeyInteractionTestsMixin): key which signed the request. """ request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) request.sign(pkey, GOOD_DIGEST) another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) with pytest.raises(Error): @@ -1679,7 +1449,7 @@ class TestX509Req(_PKeyInteractionTestsMixin): which represents the public part of the key which signed the request. """ request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) request.sign(pkey, GOOD_DIGEST) assert request.verify(pkey) @@ -1706,8 +1476,28 @@ class TestX509(_PKeyInteractionTestsMixin): """ Tests for `OpenSSL.crypto.X509`. """ + pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM - pemData = root_cert_pem + root_key_pem + extpem = """ +-----BEGIN CERTIFICATE----- +MIIC3jCCAkegAwIBAgIJAJHFjlcCgnQzMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNV +BAYTAlNFMRUwEwYDVQQIEwxXZXN0ZXJib3R0b20xEjAQBgNVBAoTCUNhdGFsb2dp +eDENMAsGA1UEAxMEUm9vdDAeFw0wODA0MjIxNDQ1MzhaFw0wOTA0MjIxNDQ1Mzha +MFQxCzAJBgNVBAYTAlNFMQswCQYDVQQIEwJXQjEUMBIGA1UEChMLT3Blbk1ldGFk +aXIxIjAgBgNVBAMTGW5vZGUxLm9tMi5vcGVubWV0YWRpci5vcmcwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBAPIcQMrwbk2nESF/0JKibj9i1x95XYAOwP+LarwT +Op4EQbdlI9SY+uqYqlERhF19w7CS+S6oyqx0DRZSk4Y9dZ9j9/xgm2u/f136YS1u +zgYFPvfUs6PqYLPSM8Bw+SjJ+7+2+TN+Tkiof9WP1cMjodQwOmdsiRbR0/J7+b1B +hec1AgMBAAGjgcQwgcEwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT +TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFIdHsBcMVVMbAO7j6NCj +03HgLnHaMB8GA1UdIwQYMBaAFL2h9Bf9Mre4vTdOiHTGAt7BRY/8MEYGA1UdEQQ/ +MD2CDSouZXhhbXBsZS5vcmeCESoub20yLmV4bWFwbGUuY29thwSC7wgKgRNvbTJA +b3Blbm1ldGFkaXIub3JnMA0GCSqGSIb3DQEBBQUAA4GBALd7WdXkp2KvZ7/PuWZA +MPlIxyjS+Ly11+BNE0xGQRp9Wz+2lABtpgNqssvU156+HkKd02rGheb2tj7MX9hG +uZzbwDAZzJPjzDQDD7d3cWsrVcfIdqVU7epHqIadnOF+X0ghJ39pAm6VVadnSXCt +WpOdIpB8KksUTCzV591Nr1wd +-----END CERTIFICATE----- + """ def signable(self): """ @@ -1717,17 +1507,21 @@ class TestX509(_PKeyInteractionTestsMixin): def test_type(self): """ - `X509` can be used to create instances of that type. + `X509` and `X509Type` refer to the same type object and can be used to + create instances of that type. """ - assert is_consistent_type(X509, "X509") + assert X509 is X509Type + assert is_consistent_type(X509, 'X509') def test_construction(self): """ - `X509` takes no arguments and returns an instance of `X509`. + `X509` takes no arguments and returns an instance of `X509Type`. """ certificate = X509() - assert isinstance(certificate, X509) - assert type(certificate).__name__ == "X509" + assert isinstance(certificate, X509Type) + assert type(X509Type).__name__ == 'type' + assert type(certificate).__name__ == 'X509' + assert type(certificate) == X509Type assert type(certificate) == X509 def test_set_version_wrong_args(self): @@ -1774,8 +1568,8 @@ class TestX509(_PKeyInteractionTestsMixin): validity period to it. """ certificate = X509() - set = getattr(certificate, "set_not" + which) - get = getattr(certificate, "get_not" + which) + set = getattr(certificate, 'set_not' + which) + get = getattr(certificate, 'get_not' + which) # Starts with no value. assert get() is None @@ -1859,8 +1653,8 @@ class TestX509(_PKeyInteractionTestsMixin): current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) - not_before_min = datetime.utcnow().replace(microsecond=0) + timedelta( - seconds=100 + not_before_min = ( + datetime.utcnow().replace(microsecond=0) + timedelta(seconds=100) ) cert.gmtime_adj_notBefore(100) not_before = datetime.strptime( @@ -1885,8 +1679,8 @@ class TestX509(_PKeyInteractionTestsMixin): to be the current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) - not_after_min = datetime.utcnow().replace(microsecond=0) + timedelta( - seconds=100 + not_after_min = ( + datetime.utcnow().replace(microsecond=0) + timedelta(seconds=100) ) cert.gmtime_adj_notAfter(100) not_after = datetime.strptime( @@ -1933,15 +1727,11 @@ class TestX509(_PKeyInteractionTestsMixin): # digest will not product the same digest). # Digest verified with the command: # openssl x509 -in root_cert.pem -noout -fingerprint -md5 - cert.digest("MD5") - == b"19:B3:05:26:2B:F8:F2:FF:0B:8F:21:07:A8:28:B8:75" - ) + cert.digest("MD5") == + b"19:B3:05:26:2B:F8:F2:FF:0B:8F:21:07:A8:28:B8:75") 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" @@ -1950,10 +1740,9 @@ class TestX509(_PKeyInteractionTestsMixin): cert.set_notAfter(when) cert.add_extensions(extensions) - cert.sign(pkey, "sha1") + cert.sign(pkey, 'sha1') return load_certificate( - FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert) - ) + FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert)) def test_extension_count(self): """ @@ -1961,11 +1750,10 @@ class TestX509(_PKeyInteractionTestsMixin): 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") + ca = X509Extension(b'basicConstraints', True, b'CA:FALSE') + key = X509Extension(b'keyUsage', True, b'digitalSignature') subjectAltName = X509Extension( - b"subjectAltName", True, b"DNS:example.com" - ) + b'subjectAltName', True, b'DNS:example.com') # Try a certificate with no extensions at all. c = self._extcert(pkey, []) @@ -1985,28 +1773,27 @@ class TestX509(_PKeyInteractionTestsMixin): `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") + ca = X509Extension(b'basicConstraints', True, b'CA:FALSE') + key = X509Extension(b'keyUsage', True, b'digitalSignature') subjectAltName = X509Extension( - b"subjectAltName", False, b"DNS:example.com" - ) + 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" + 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" + 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" + assert ext.get_short_name() == b'subjectAltName' with pytest.raises(IndexError): cert.get_extension(-1) @@ -2024,14 +1811,13 @@ class TestX509(_PKeyInteractionTestsMixin): cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) ext = cert.get_extension(3) - assert ext.get_short_name() == b"subjectAltName" + 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\n" - == str(ext).encode("ascii") - ) + b"IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1\n" == + str(ext).encode("ascii")) def test_invalid_digest_algorithm(self): """ @@ -2049,13 +1835,10 @@ class TestX509(_PKeyInteractionTestsMixin): 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"), - ] + 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): """ @@ -2073,13 +1856,12 @@ class TestX509(_PKeyInteractionTestsMixin): """ cert = X509() name = cert.get_subject() - name.C = "AU" - name.OU = "Unit Tests" + 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"), - ] + assert ( + cert.get_subject().get_components() == + [(b'C', b'AU'), (b'OU', b'Unit Tests')]) def test_get_issuer(self): """ @@ -2089,13 +1871,10 @@ class TestX509(_PKeyInteractionTestsMixin): 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"), - ] + 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): """ @@ -2113,13 +1892,12 @@ class TestX509(_PKeyInteractionTestsMixin): """ cert = X509() name = cert.get_issuer() - name.C = "AU" - name.OU = "Unit Tests" + 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"), - ] + assert ( + cert.get_issuer().get_components() == + [(b'C', b'AU'), (b'OU', b'Unit Tests')]) def test_get_pubkey_uninitialized(self): """ @@ -2145,8 +1923,10 @@ class TestX509(_PKeyInteractionTestsMixin): subject name. """ cert = load_certificate(FILETYPE_PEM, self.pemData) - # SHA1 - assert cert.subject_name_hash() == 3278919224 + assert cert.subject_name_hash() in [ + 3350047874, # OpenSSL 0.9.8, MD5 + 3278919224, # OpenSSL 1.0.0, SHA1 + ] def test_get_signature_algorithm(self): """ @@ -2154,7 +1934,7 @@ class TestX509(_PKeyInteractionTestsMixin): the algorithm used to sign the certificate. """ cert = load_certificate(FILETYPE_PEM, self.pemData) - assert b"sha256WithRSAEncryption" == cert.get_signature_algorithm() + assert b"sha1WithRSAEncryption" == cert.get_signature_algorithm() def test_get_undefined_signature_algorithm(self): """ @@ -2226,17 +2006,18 @@ class TestX509Store(object): """ `X509Store` is a type object. """ - assert is_consistent_type(X509Store, "X509Store") + assert X509Store is X509StoreType + 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) + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) store = X509Store() store.add_cert(cert) - @pytest.mark.parametrize("cert", [None, 1.0, "cert", object()]) + @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 @@ -2251,84 +2032,24 @@ class TestX509Store(object): `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) + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) 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(object): - 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)) - class TestPKCS12(object): """ Test for `OpenSSL.crypto.PKCS12` and `OpenSSL.crypto.load_pkcs12`. """ + pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM def test_type(self): """ - `PKCS12` is a type object. + `PKCS12Type` is a type object. """ - assert is_consistent_type(PKCS12, "PKCS12") + assert PKCS12 is PKCS12Type + assert is_consistent_type(PKCS12, 'PKCS12') def test_empty_construction(self): """ @@ -2351,13 +2072,13 @@ class TestPKCS12(object): for bad_arg in [3, PKey(), X509]: with pytest.raises(TypeError): p12.set_certificate(bad_arg) - for bad_arg in [3, "legbone", X509()]: + for bad_arg in [3, 'legbone', X509()]: with pytest.raises(TypeError): p12.set_privatekey(bad_arg) for bad_arg in [3, X509(), (3, 4), (PKey(),)]: with pytest.raises(TypeError): p12.set_ca_certificates(bad_arg) - for bad_arg in [6, ("foo", "bar")]: + for bad_arg in [6, ('foo', 'bar')]: with pytest.raises(TypeError): p12.set_friendlyname(bad_arg) @@ -2368,7 +2089,7 @@ class TestPKCS12(object): """ passwd = b"blah" p12 = PKCS12() - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) p12.set_privatekey(pkey) assert None is p12.get_certificate() assert pkey == p12.get_privatekey() @@ -2394,7 +2115,7 @@ class TestPKCS12(object): """ passwd = b"blah" p12 = PKCS12() - cert = load_certificate(FILETYPE_PEM, root_cert_pem) + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) p12.set_certificate(cert) assert cert == p12.get_certificate() assert None is p12.get_privatekey() @@ -2417,13 +2138,12 @@ class TestPKCS12(object): # it to. At some point, hopefully this will change so that # p12.get_certificate() is actually what returns the loaded # certificate. - assert root_cert_pem == dump_certificate( - FILETYPE_PEM, p12.get_ca_certificates()[0] - ) + assert ( + cleartextCertificatePEM == + dump_certificate(FILETYPE_PEM, p12.get_ca_certificates()[0])) - def gen_pkcs12( - self, cert_pem=None, key_pem=None, ca_pem=None, friendly_name=None - ): + def gen_pkcs12(self, cert_pem=None, key_pem=None, ca_pem=None, + friendly_name=None): """ Generate a PKCS12 object with components from PEM. Verify that the set functions return None. @@ -2445,48 +2165,27 @@ class TestPKCS12(object): assert ret is None return p12 - def check_recovery( - self, p12_str, key=None, cert=None, ca=None, passwd=b"", extra=() - ): + def check_recovery(self, p12_str, key=None, cert=None, ca=None, passwd=b"", + extra=()): """ Use openssl program to confirm three components are recoverable from a PKCS12 string. """ if key: recovered_key = _runopenssl( - p12_str, - b"pkcs12", - b"-nocerts", - b"-nodes", - b"-passin", - b"pass:" + passwd, - *extra - ) - assert recovered_key[-len(key) :] == key + p12_str, b"pkcs12", b"-nocerts", b"-nodes", b"-passin", + b"pass:" + passwd, *extra) + assert recovered_key[-len(key):] == key if cert: recovered_cert = _runopenssl( - p12_str, - b"pkcs12", - b"-clcerts", - b"-nodes", - b"-passin", - b"pass:" + passwd, - b"-nokeys", - *extra - ) - assert recovered_cert[-len(cert) :] == cert + p12_str, b"pkcs12", b"-clcerts", b"-nodes", b"-passin", + b"pass:" + passwd, b"-nokeys", *extra) + assert recovered_cert[-len(cert):] == cert if ca: recovered_cert = _runopenssl( - p12_str, - b"pkcs12", - b"-cacerts", - b"-nodes", - b"-passin", - b"pass:" + passwd, - b"-nokeys", - *extra - ) - assert recovered_cert[-len(ca) :] == ca + p12_str, b"pkcs12", b"-cacerts", b"-nodes", b"-passin", + b"pass:" + passwd, b"-nokeys", *extra) + assert recovered_cert[-len(ca):] == ca def verify_pkcs12_container(self, p12): """ @@ -2498,11 +2197,9 @@ class TestPKCS12(object): """ cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate()) key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey()) - assert (client_cert_pem, client_key_pem, None) == ( - cert_pem, - key_pem, - p12.get_ca_certificates(), - ) + assert ( + (client_cert_pem, client_key_pem, None) == + (cert_pem, key_pem, p12.get_ca_certificates())) def test_load_pkcs12(self): """ @@ -2517,7 +2214,7 @@ class TestPKCS12(object): b"-export", b"-clcerts", b"-passout", - b"pass:" + passwd, + b"pass:" + passwd ) p12 = load_pkcs12(p12_str, passphrase=passwd) self.verify_pkcs12_container(p12) @@ -2530,21 +2227,15 @@ class TestPKCS12(object): """ pem = client_key_pem + client_cert_pem passwd = b"whatever" - p12_str = _runopenssl( - pem, - b"pkcs12", - b"-export", - b"-clcerts", - b"-passout", - b"pass:" + passwd, - ) + p12_str = _runopenssl(pem, b"pkcs12", b"-export", b"-clcerts", + b"-passout", b"pass:" + passwd) with pytest.warns(DeprecationWarning) as w: simplefilter("always") p12 = load_pkcs12(p12_str, passphrase=b"whatever".decode("ascii")) - msg = "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) - assert msg == str(w[-1].message) + assert ( + "{0} for passphrase is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) self.verify_pkcs12_container(p12) @@ -2556,8 +2247,7 @@ class TestPKCS12(object): """ pem = client_key_pem + client_cert_pem p12_str = _runopenssl( - pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" - ) + pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:") p12 = load_pkcs12(p12_str) self.verify_pkcs12_container(p12) @@ -2576,8 +2266,7 @@ class TestPKCS12(object): extracted and examined. """ self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=None, load_passphrase=b"") - ) + self._dump_and_load(dump_passphrase=None, load_passphrase=b'')) def test_load_pkcs12_null_passphrase_load_null(self): """ @@ -2586,8 +2275,7 @@ class TestPKCS12(object): extracted and examined. """ self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=None, load_passphrase=None) - ) + self._dump_and_load(dump_passphrase=None, load_passphrase=None)) def test_load_pkcs12_empty_passphrase_load_empty(self): """ @@ -2596,8 +2284,7 @@ class TestPKCS12(object): extracted and examined. """ self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=b"", load_passphrase=b"") - ) + self._dump_and_load(dump_passphrase=b'', load_passphrase=b'')) def test_load_pkcs12_empty_passphrase_load_null(self): """ @@ -2606,18 +2293,17 @@ class TestPKCS12(object): extracted and examined. """ self.verify_pkcs12_container( - self._dump_and_load(dump_passphrase=b"", load_passphrase=None) - ) + self._dump_and_load(dump_passphrase=b'', load_passphrase=None)) def test_load_pkcs12_garbage(self): """ `load_pkcs12` raises `OpenSSL.crypto.Error` when passed a string which is not a PKCS12 dump. """ - passwd = b"whatever" + passwd = 'whatever' with pytest.raises(Error) as err: - load_pkcs12(b"fruit loops", passwd) - assert err.value.args[0][0][0] == "asn1 encoding routines" + load_pkcs12(b'fruit loops', passwd) + assert err.value.args[0][0][0] == 'asn1 encoding routines' assert len(err.value.args[0][0]) == 3 def test_replace(self): @@ -2647,7 +2333,7 @@ class TestPKCS12(object): """ passwd = b'Dogmeat[]{}!@#$%^&*()~`?/.,<>-_+=";:' p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) - for friendly_name in [b"Serverlicious", None, b"###"]: + for friendly_name in [b'Serverlicious', None, b'###']: p12.set_friendlyname(friendly_name) assert p12.get_friendlyname() == friendly_name dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) @@ -2658,12 +2344,8 @@ class TestPKCS12(object): # does not store the friendly name in the cert's # alias, which we could then extract. self.check_recovery( - dumped_p12, - key=server_key_pem, - cert=server_cert_pem, - ca=root_cert_pem, - passwd=passwd, - ) + dumped_p12, key=server_key_pem, cert=server_cert_pem, + ca=root_cert_pem, passwd=passwd) def test_various_empty_passphrases(self): """ @@ -2677,12 +2359,8 @@ class TestPKCS12(object): dumped_p12_nopw = p12.export(iter=9, maciter=4) for dumped_p12 in [dumped_p12_empty, dumped_p12_none, dumped_p12_nopw]: self.check_recovery( - dumped_p12, - key=client_key_pem, - cert=client_cert_pem, - ca=root_cert_pem, - passwd=passwd, - ) + dumped_p12, key=client_key_pem, cert=client_cert_pem, + ca=root_cert_pem, passwd=passwd) def test_removing_ca_cert(self): """ @@ -2701,12 +2379,8 @@ class TestPKCS12(object): p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2) self.check_recovery( - dumped_p12, - key=server_key_pem, - cert=server_cert_pem, - passwd=passwd, - extra=(b"-nomacver",), - ) + dumped_p12, key=server_key_pem, cert=server_cert_pem, + passwd=passwd, extra=(b"-nomacver",)) def test_load_without_mac(self): """ @@ -2732,14 +2406,14 @@ class TestPKCS12(object): """ A PKCS12 with an empty CA certificates list can be exported. """ - passwd = b"Hobie 18" + passwd = b'Hobie 18' p12 = self.gen_pkcs12(server_cert_pem, server_key_pem) p12.set_ca_certificates([]) assert () == p12.get_ca_certificates() dumped_p12 = p12.export(passphrase=passwd, iter=3) self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=passwd - ) + dumped_p12, key=server_key_pem, cert=server_cert_pem, + passwd=passwd) def test_export_without_args(self): """ @@ -2748,8 +2422,7 @@ class TestPKCS12(object): p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) dumped_p12 = p12.export() # no args self.check_recovery( - dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"" - ) + dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"") def test_export_without_bytes(self): """ @@ -2760,15 +2433,15 @@ class TestPKCS12(object): with pytest.warns(DeprecationWarning) as w: simplefilter("always") dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii")) - msg = "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) - assert msg == str(w[-1].message) + assert ( + "{0} for passphrase is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) self.check_recovery( dumped_p12, key=server_key_pem, cert=server_cert_pem, - passwd=b"randomtext", + passwd=b"randomtext" ) def test_key_cert_mismatch(self): @@ -2799,7 +2472,6 @@ class TestLoadPublicKey(object): """ Tests for :func:`load_publickey`. """ - def test_loading_works(self): """ load_publickey loads public keys and sets correct attributes. @@ -2828,7 +2500,7 @@ class TestLoadPublicKey(object): """ load_publickey works with text strings, not just bytes. """ - serialized = cleartextPublicKeyPEM.decode("ascii") + serialized = cleartextPublicKeyPEM.decode('ascii') key = load_publickey(FILETYPE_PEM, serialized) dumped_pem = dump_publickey(FILETYPE_PEM, key) @@ -2854,8 +2526,7 @@ class TestFunction(object): """ with pytest.raises(TypeError): load_privatekey( - FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object() - ) + FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object()) def test_load_privatekey_wrongPassphrase(self): """ @@ -2872,7 +2543,7 @@ class TestFunction(object): with a private key encoded in a format, that doesn't support encryption. """ - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) blob = dump_privatekey(FILETYPE_ASN1, key) with pytest.raises(ValueError): load_privatekey(FILETYPE_ASN1, blob, "secret") @@ -2883,18 +2554,15 @@ class TestFunction(object): string if given the passphrase. """ key = load_privatekey( - FILETYPE_PEM, - encryptedPrivateKeyPEM, - encryptedPrivateKeyPEMPassphrase, - ) - assert isinstance(key, PKey) + FILETYPE_PEM, encryptedPrivateKeyPEM, + encryptedPrivateKeyPEMPassphrase) + assert isinstance(key, PKeyType) 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 @@ -2912,7 +2580,6 @@ class TestFunction(object): def cb(*a): called.append(None) return b"quack" - with pytest.raises(Error) as err: load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) assert called @@ -2929,9 +2596,8 @@ class TestFunction(object): def cb(writing): called.append(writing) return encryptedPrivateKeyPEMPassphrase - key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - assert isinstance(key, PKey) + assert isinstance(key, PKeyType) assert called == [False] def test_load_privatekey_passphrase_wrong_return_type(self): @@ -2941,8 +2607,7 @@ class TestFunction(object): """ with pytest.raises(ValueError): load_privatekey( - FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3 - ) + FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3) def test_dump_privatekey_wrong_args(self): """ @@ -3003,7 +2668,6 @@ class TestFunction(object): `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 @@ -3015,11 +2679,11 @@ class TestFunction(object): `dump_privatekey` writes an encrypted PEM when given a passphrase. """ passphrase = b"foo" - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase) - assert isinstance(pem, bytes) + assert isinstance(pem, binary_type) loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) - assert isinstance(loadedKey, PKey) + assert isinstance(loadedKey, PKeyType) assert loadedKey.type() == key.type() assert loadedKey.bits() == key.bits() @@ -3029,7 +2693,7 @@ class TestFunction(object): with a private key encoded in a format, that doesn't support encryption. """ - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) with pytest.raises(ValueError): dump_privatekey(FILETYPE_ASN1, key, GOOD_CIPHER, "secret") @@ -3037,25 +2701,27 @@ class TestFunction(object): """ `dump_certificate` writes PEM, DER, and text. """ - pemData = root_cert_pem + root_key_pem + pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM cert = load_certificate(FILETYPE_PEM, pemData) dumped_pem = dump_certificate(FILETYPE_PEM, cert) - assert dumped_pem == root_cert_pem + assert dumped_pem == cleartextCertificatePEM 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 + assert dumped_pem2 == cleartextCertificatePEM dumped_text = dump_certificate(FILETYPE_TEXT, cert) - assert len(dumped_text) > 500 + good_text = _runopenssl( + dumped_pem, b"x509", b"-noout", b"-text", b"-nameopt", b"") + assert dumped_text == good_text 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) + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) with pytest.raises(ValueError): dump_certificate(object(), cert) @@ -3063,35 +2729,36 @@ class TestFunction(object): """ `dump_privatekey` writes a PEM """ - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) assert key.check() dumped_pem = dump_privatekey(FILETYPE_PEM, key) - assert dumped_pem == normalized_root_key_pem + assert dumped_pem == cleartextPrivateKeyPEM def test_dump_privatekey_asn1(self): """ `dump_privatekey` writes a DER """ - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + dumped_pem = dump_privatekey(FILETYPE_PEM, key) 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 + # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad. + good_der = _runopenssl(dumped_pem, b"rsa", b"-outform", b"DER") + assert dumped_der == good_der + key2 = load_privatekey(FILETYPE_ASN1, dumped_der) + dumped_pem2 = dump_privatekey(FILETYPE_PEM, key2) + assert dumped_pem2 == cleartextPrivateKeyPEM def test_dump_privatekey_text(self): """ `dump_privatekey` writes a text """ - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + dumped_pem = dump_privatekey(FILETYPE_PEM, key) + dumped_text = dump_privatekey(FILETYPE_TEXT, key) - assert len(dumped_text) > 500 + good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text") + assert dumped_text == good_text def test_dump_publickey_pem(self): """ @@ -3125,8 +2792,7 @@ class TestFunction(object): `dump_certificate_request` writes a PEM, DER, and text. """ req = load_certificate_request( - FILETYPE_PEM, cleartextCertificateRequestPEM - ) + FILETYPE_PEM, cleartextCertificateRequestPEM) dumped_pem = dump_certificate_request(FILETYPE_PEM, req) assert dumped_pem == cleartextCertificateRequestPEM dumped_der = dump_certificate_request(FILETYPE_ASN1, req) @@ -3136,7 +2802,9 @@ class TestFunction(object): 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 + good_text = _runopenssl( + dumped_pem, b"req", b"-noout", b"-text", b"-nameopt", b"") + assert dumped_text == good_text with pytest.raises(ValueError): dump_certificate_request(100, req) @@ -3151,13 +2819,12 @@ class TestFunction(object): def cb(writing): called.append(writing) return passphrase - - key = load_privatekey(FILETYPE_PEM, root_key_pem) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) - assert isinstance(pem, bytes) + assert isinstance(pem, binary_type) assert called == [True] loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) - assert isinstance(loadedKey, PKey) + assert isinstance(loadedKey, PKeyType) assert loadedKey.type() == key.type() assert loadedKey.bits() == key.bits() @@ -3166,11 +2833,10 @@ class TestFunction(object): `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) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) with pytest.raises(ArithmeticError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) @@ -3179,52 +2845,13 @@ class TestFunction(object): `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) + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) 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) - def test_load_pkcs7_data_pem(self): """ `load_pkcs7_data` accepts a PKCS#7 string and returns an instance of @@ -3285,6 +2912,14 @@ class TestPKCS7(object): Tests for `PKCS7`. """ + def test_type(self): + """ + `PKCS7` is a type object. + """ + assert isinstance(PKCS7, type) + assert PKCS7Type.__name__ == 'PKCS7' + assert PKCS7 is PKCS7Type + def test_type_is_signed(self): """ `PKCS7.type_is_signed` returns `True` if the PKCS7 object is of @@ -3323,7 +2958,7 @@ class TestPKCS7(object): type name. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) - assert pkcs7.get_type_name() == b"pkcs7-signedData" + assert pkcs7.get_type_name() == b'pkcs7-signedData' def test_attribute(self): """ @@ -3348,16 +2983,18 @@ class TestNetscapeSPKI(_PKeyInteractionTestsMixin): def test_type(self): """ - `NetscapeSPKI` can be used to create instances of that type. + `NetscapeSPKI` and `NetscapeSPKIType` refer to the same type object + and can be used to create instances of that type. """ - assert is_consistent_type(NetscapeSPKI, "NetscapeSPKI") + assert NetscapeSPKI is NetscapeSPKIType + assert is_consistent_type(NetscapeSPKI, 'NetscapeSPKI') def test_construction(self): """ - `NetscapeSPKI` returns an instance of `NetscapeSPKI`. + `NetscapeSPKI` returns an instance of `NetscapeSPKIType`. """ nspki = NetscapeSPKI() - assert isinstance(nspki, NetscapeSPKI) + assert isinstance(nspki, NetscapeSPKIType) def test_invalid_attribute(self): """ @@ -3374,14 +3011,13 @@ class TestNetscapeSPKI(_PKeyInteractionTestsMixin): """ nspki = NetscapeSPKI() blob = nspki.b64_encode() - assert isinstance(blob, bytes) + assert isinstance(blob, binary_type) class TestRevoked(object): """ 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 @@ -3391,7 +3027,7 @@ class TestRevoked(object): crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) revoked = crl.get_revoked() reason = revoked[1].get_reason() - assert reason == b"Unspecified" + assert reason == b'Unspecified' def test_ignores_unsupported_revoked_cert_extension_set_new_reason(self): crl = load_crl(FILETYPE_PEM, crlDataUnsupportedExtension) @@ -3408,7 +3044,7 @@ class TestRevoked(object): revoked = Revoked() assert isinstance(revoked, Revoked) assert type(revoked) == Revoked - assert revoked.get_serial() == b"00" + assert revoked.get_serial() == b'00' assert revoked.get_rev_date() is None assert revoked.get_reason() is None @@ -3418,17 +3054,17 @@ class TestRevoked(object): `OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. """ revoked = Revoked() - ret = revoked.set_serial(b"10b") + ret = revoked.set_serial(b'10b') assert ret is None ser = revoked.get_serial() - assert ser == b"010B" + assert ser == b'010B' - revoked.set_serial(b"31ppp") # a type error would be nice + revoked.set_serial(b'31ppp') # a type error would be nice ser = revoked.get_serial() - assert ser == b"31" + assert ser == b'31' with pytest.raises(ValueError): - revoked.set_serial(b"pqrst") + revoked.set_serial(b'pqrst') with pytest.raises(TypeError): revoked.set_serial(100) @@ -3459,15 +3095,15 @@ class TestRevoked(object): ret = revoked.set_reason(r) assert ret is None reason = revoked.get_reason() - assert reason.lower().replace(b" ", b"") == r.lower().replace( - b" ", b"" - ) + 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, u"foo"]) + @pytest.mark.parametrize('reason', [object(), 1.0, u'foo']) def test_set_reason_wrong_args(self, reason): """ `Revoked.set_reason` raises `TypeError` if called with an argument @@ -3484,27 +3120,24 @@ class TestRevoked(object): """ revoked = Revoked() with pytest.raises(ValueError): - revoked.set_reason(b"blue") + revoked.set_reason(b'blue') class TestCRL(object): """ Tests for `OpenSSL.crypto.CRL`. """ - - cert = load_certificate(FILETYPE_PEM, root_cert_pem) - pkey = load_privatekey(FILETYPE_PEM, root_key_pem) + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) 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 - ) + FILETYPE_PEM, intermediate_server_cert_pem) intermediate_server_key = load_privatekey( - FILETYPE_PEM, intermediate_server_key_pem - ) + FILETYPE_PEM, intermediate_server_key_pem) def test_construction(self): """ @@ -3523,8 +3156,8 @@ class TestCRL(object): 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") + revoked.set_serial(b'3ab') + revoked.set_reason(b'sUpErSeDEd') crl.add_revoked(revoked) return crl @@ -3541,17 +3174,13 @@ class TestCRL(object): crl = x509.load_pem_x509_crl(dumped_crl, backend) 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, u"US"), - x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, u"IL"), - x509.NameAttribute(x509.NameOID.LOCALITY_NAME, u"Chicago"), - x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, u"Testing"), - x509.NameAttribute( - x509.NameOID.COMMON_NAME, u"Testing Root CA" - ), - ] - ) + assert crl.issuer == x509.Name([ + x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, u"IL"), + x509.NameAttribute(x509.NameOID.LOCALITY_NAME, u"Chicago"), + x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, u"Testing"), + x509.NameAttribute(x509.NameOID.COMMON_NAME, u"Testing Root CA"), + ]) def test_export_der(self): """ @@ -3568,18 +3197,17 @@ class TestCRL(object): crl = x509.load_der_x509_crl(dumped_crl, backend) 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, u"US"), - x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, u"IL"), - x509.NameAttribute(x509.NameOID.LOCALITY_NAME, u"Chicago"), - x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, u"Testing"), - x509.NameAttribute( - x509.NameOID.COMMON_NAME, u"Testing Root CA" - ), - ] - ) - + assert crl.issuer == x509.Name([ + x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.NameOID.STATE_OR_PROVINCE_NAME, u"IL"), + x509.NameAttribute(x509.NameOID.LOCALITY_NAME, u"Chicago"), + x509.NameAttribute(x509.NameOID.ORGANIZATION_NAME, u"Testing"), + x509.NameAttribute(x509.NameOID.COMMON_NAME, u"Testing Root CA"), + ]) + + # Flaky because we compare the output of running commands which sometimes + # varies by 1 second + @flaky.flaky def test_export_text(self): """ If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a @@ -3588,11 +3216,19 @@ class TestCRL(object): """ crl = self._get_crl() + dumped_crl = crl.export( + self.cert, self.pkey, FILETYPE_ASN1, digest=b"md5" + ) + text = _runopenssl( + dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER", + b"-nameopt", b"" + ) + # text format dumped_text = crl.export( self.cert, self.pkey, type=FILETYPE_TEXT, digest=b"md5" ) - assert len(dumped_text) > 500 + assert text == dumped_text def test_export_custom_digest(self): """ @@ -3602,7 +3238,7 @@ class TestCRL(object): crl = self._get_crl() dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha1") text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") - text.index(b"Signature Algorithm: sha1") + text.index(b'Signature Algorithm: sha1') def test_export_md5_digest(self): """ @@ -3615,7 +3251,7 @@ class TestCRL(object): 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") + text.index(b'Signature Algorithm: md5') def test_export_default_digest(self): """ @@ -3681,8 +3317,7 @@ class TestCRL(object): crl = CRL() with pytest.raises(ValueError): crl.export( - self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" - ) + self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest") def test_get_revoked(self): """ @@ -3694,18 +3329,18 @@ class TestCRL(object): 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_serial(b'3ab') crl.add_revoked(revoked) - revoked.set_serial(b"100") - revoked.set_reason(b"sUpErSeDEd") + 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]) == Revoked assert type(revs[1]) == Revoked - assert revs[0].get_serial() == b"03AB" - assert revs[1].get_serial() == b"0100" + 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 @@ -3717,19 +3352,19 @@ class TestCRL(object): 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_serial() == b'03AB' assert revs[0].get_reason() is None - assert revs[1].get_serial() == b"0100" - assert revs[1].get_reason() == b"Superseded" + 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_serial() == b'03AB' assert revs[0].get_reason() is None - assert revs[1].get_serial() == b"0100" - assert revs[1].get_reason() == b"Superseded" + assert revs[1].get_serial() == b'0100' + assert revs[1].get_reason() == b'Superseded' def test_load_crl_bad_filetype(self): """ @@ -3754,7 +3389,7 @@ class TestCRL(object): """ crl = load_crl(FILETYPE_PEM, crlData) assert isinstance(crl.get_issuer(), X509Name) - assert crl.get_issuer().CN == "Testing Root CA" + assert crl.get_issuer().CN == 'Testing Root CA' def test_dump_crl(self): """ @@ -3777,15 +3412,15 @@ class TestCRL(object): # 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") + 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") + revoked.set_reason(b'unspecified') + revoked.set_rev_date(b'20140601000000Z') crl.add_revoked(revoked) crl.set_version(1) - crl.set_lastUpdate(b"20140601000000Z") - crl.set_nextUpdate(b"20180601000000Z") - crl.sign(issuer_cert, issuer_key, digest=b"sha512") + crl.set_lastUpdate(b'20140601000000Z') + crl.set_nextUpdate(b'20180601000000Z') + crl.sign(issuer_cert, issuer_key, digest=b'sha512') return crl def test_verify_with_revoked(self): @@ -3797,20 +3432,17 @@ class TestCRL(object): store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) root_crl = self._make_test_crl( - self.root_cert, self.root_key, certs=[self.intermediate_cert] - ) + self.root_cert, self.root_key, certs=[self.intermediate_cert]) intermediate_crl = self._make_test_crl( - self.intermediate_cert, self.intermediate_key, certs=[] - ) + 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 - ) + 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 err.value.args[0][2] == "certificate revoked" + assert err.value.args[0][2] == 'certificate revoked' def test_verify_with_missing_crl(self): """ @@ -3821,17 +3453,15 @@ class TestCRL(object): store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) root_crl = self._make_test_crl( - self.root_cert, self.root_key, certs=[self.intermediate_cert] - ) + 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 - ) + 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 err.value.args[0][2] == "unable to get certificate CRL" - assert err.value.certificate.get_subject().CN == "intermediate-service" + assert err.value.args[0][2] == '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, backend) @@ -3852,12 +3482,10 @@ class TestX509StoreContext(object): """ 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 - ) + FILETYPE_PEM, intermediate_server_cert_pem) def test_valid(self): """ @@ -3882,145 +3510,6 @@ class TestX509StoreContext(object): 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 @@ -4041,8 +3530,8 @@ class TestX509StoreContext(object): with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == "self signed certificate" - assert exc.value.certificate.get_subject().CN == "Testing Root CA" + assert exc.value.args[0][2] == 'self signed certificate' + assert exc.value.certificate.get_subject().CN == 'Testing Root CA' def test_invalid_chain_no_root(self): """ @@ -4056,8 +3545,8 @@ class TestX509StoreContext(object): with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == "unable to get issuer certificate" - assert exc.value.certificate.get_subject().CN == "intermediate" + assert exc.value.args[0][2] == 'unable to get issuer certificate' + assert exc.value.certificate.get_subject().CN == 'intermediate' def test_invalid_chain_no_intermediate(self): """ @@ -4071,8 +3560,8 @@ class TestX509StoreContext(object): with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == "unable to get local issuer certificate" - assert exc.value.certificate.get_subject().CN == "intermediate-service" + assert exc.value.args[0][2] == 'unable to get local issuer certificate' + assert exc.value.certificate.get_subject().CN == 'intermediate-service' def test_modification_pre_verify(self): """ @@ -4089,8 +3578,8 @@ class TestX509StoreContext(object): with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == "unable to get issuer certificate" - assert exc.value.certificate.get_subject().CN == "intermediate" + assert exc.value.args[0][2] == '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 @@ -4106,7 +3595,7 @@ class TestX509StoreContext(object): expire_time = self.intermediate_server_cert.get_notAfter() expire_datetime = datetime.strptime( - expire_time.decode("utf-8"), "%Y%m%d%H%M%SZ" + expire_time.decode('utf-8'), '%Y%m%d%H%M%SZ' ) store.set_time(expire_datetime) @@ -4114,106 +3603,7 @@ class TestX509StoreContext(object): with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == "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 exc.value.args[0][2] == "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 = "{:08x}.0".format(cacert.subject_name_hash()) - 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 exc.value.args[0][2] == "unable to get local issuer certificate" + assert exc.value.args[0][2] == 'certificate has expired' class TestSignVerify(object): @@ -4230,8 +3620,7 @@ class TestSignVerify(object): 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." - ) + 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) @@ -4240,7 +3629,7 @@ class TestSignVerify(object): # certificate unrelated to priv_key, used to trigger an error bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) - for digest in ["md5", "sha1"]: + for digest in ['md5', 'sha1']: sig = sign(priv_key, content, digest) # Verify the signature of content, will throw an exception if @@ -4279,20 +3668,22 @@ class TestSignVerify(object): priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) cert = load_certificate(FILETYPE_PEM, root_cert_pem) - for digest in ["md5", "sha1"]: + for digest in ['md5', 'sha1']: with pytest.warns(DeprecationWarning) as w: simplefilter("always") sig = sign(priv_key, content, digest) - assert "{0} for data is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message) + assert ( + "{0} for data is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) with pytest.warns(DeprecationWarning) as w: simplefilter("always") verify(cert, sig, content, digest) - assert "{0} for data is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message) + assert ( + "{0} for data is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) def test_sign_verify_ecdsa(self): """ @@ -4306,7 +3697,7 @@ class TestSignVerify(object): 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, ec_root_key_pem) cert = load_certificate(FILETYPE_PEM, ec_root_cert_pem) sig = sign(priv_key, content, "sha1") @@ -4331,8 +3722,7 @@ class TestSignVerify(object): 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." - ) + 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, "sha1") @@ -4404,7 +3794,6 @@ class TestEllipticCurveEquality(EqualityTestsMixin): """ Tests `_EllipticCurve`'s implementation of ``==`` and ``!=``. """ - curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: @@ -4429,7 +3818,6 @@ class TestEllipticCurveHash(object): 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: @@ -4450,7 +3838,7 @@ class TestEllipticCurveHash(object): 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)] - ) + curves = set([ + get_elliptic_curve(self.curve_factory.another_curve_name) + ]) assert curve not in curves diff --git a/tests/test_rand.py b/tests/test_rand.py index 763d711..e04a24c 100644 --- a/tests/test_rand.py +++ b/tests/test_rand.py @@ -11,7 +11,11 @@ from OpenSSL import rand class TestRand(object): - @pytest.mark.parametrize("args", [(b"foo", None), (None, 3)]) + + @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 @@ -24,7 +28,7 @@ class TestRand(object): """ `OpenSSL.rand.add` adds entropy to the PRNG. """ - rand.add(b"hamburger", 3) + rand.add(b'hamburger', 3) def test_status(self): """ diff --git a/tests/test_ssl.py b/tests/test_ssl.py index 8fdcae2..bddeaa9 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py @@ -6,33 +6,23 @@ Unit tests for :mod:`OpenSSL.SSL`. """ import datetime -import gc import sys import uuid from gc import collect, get_referrers -from errno import ( - EAFNOSUPPORT, - ECONNREFUSED, - EINPROGRESS, - EWOULDBLOCK, - EPIPE, - ESHUTDOWN, -) +from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN from sys import platform, getfilesystemencoding -from socket import AF_INET, AF_INET6, MSG_PEEK, SHUT_RDWR, error, socket +from socket import MSG_PEEK, SHUT_RDWR, error, socket from os import makedirs from os.path import join from weakref import ref from warnings import simplefilter -import flaky - import pytest from pretend import raiser -from six import PY2, text_type +from six import PY3, text_type from cryptography import x509 from cryptography.hazmat.backends import default_backend @@ -52,125 +42,63 @@ from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN from OpenSSL.SSL import ( - SSLv2_METHOD, - SSLv3_METHOD, - SSLv23_METHOD, - TLSv1_METHOD, - TLSv1_1_METHOD, - TLSv1_2_METHOD, -) + SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, + TLSv1_1_METHOD, TLSv1_2_METHOD) from OpenSSL.SSL import OP_SINGLE_DH_USE, OP_NO_SSLv2, OP_NO_SSLv3 from OpenSSL.SSL import ( - VERIFY_PEER, - VERIFY_FAIL_IF_NO_PEER_CERT, - VERIFY_CLIENT_ONCE, - VERIFY_NONE, -) + VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, VERIFY_NONE) from OpenSSL import SSL from OpenSSL.SSL import ( - 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, -) + 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) from OpenSSL.SSL import ( - Error, - SysCallError, - WantReadError, - WantWriteError, - ZeroReturnError, -) -from OpenSSL.SSL import Context, Session, Connection, SSLeay_version + Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) +from OpenSSL.SSL import ( + Context, ContextType, Session, Connection, ConnectionType, SSLeay_version) from OpenSSL.SSL import _make_requires from OpenSSL._util import ffi as _ffi, lib as _lib from OpenSSL.SSL import ( - OP_NO_QUERY_MTU, - OP_COOKIE_EXCHANGE, - OP_NO_TICKET, - OP_NO_COMPRESSION, - MODE_RELEASE_BUFFERS, - NO_OVERLAPPING_PROTOCOLS, -) + OP_NO_QUERY_MTU, OP_COOKIE_EXCHANGE, OP_NO_TICKET, OP_NO_COMPRESSION, + MODE_RELEASE_BUFFERS) from OpenSSL.SSL import ( - 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, -) + 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) try: from OpenSSL.SSL import ( - SSL_ST_INIT, - SSL_ST_BEFORE, - SSL_ST_OK, - SSL_ST_RENEGOTIATE, + SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE ) except ImportError: SSL_ST_INIT = SSL_ST_BEFORE = SSL_ST_OK = SSL_ST_RENEGOTIATE = None from .util import WARNING_TYPE_EXPECTED, NON_ASCII, is_consistent_type from .test_crypto import ( - client_cert_pem, - client_key_pem, - server_cert_pem, - server_key_pem, - root_cert_pem, - root_key_pem, -) + cleartextCertificatePEM, cleartextPrivateKeyPEM, + client_cert_pem, client_key_pem, server_cert_pem, server_key_pem, + root_cert_pem) -# openssl dhparam 2048 -out dh-2048.pem +# openssl dhparam 1024 -out dh-1024.pem (note that 1024 is a small number of +# bits to use) 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== +MIGHAoGBALdUMvn+C9MM+y5BWZs11mSeH6HHoEq0UVbzVq7UojC1hbsZUuGukQ3a +Qh2/pwqb18BZFykrWB0zv/OkLa0kx4cuUgNrUVq1EFheBiX6YqryJ7t2sO09NQiO +V7H54LmltOT/hEh6QWsJqb6BQgH65bswvV/XkYGja8/T0GzvbaVzAgEC -----END DH PARAMETERS----- """ -skip_if_py3 = pytest.mark.skipif(not PY2, reason="Python 2 only") - - -def socket_any_family(): - try: - return socket(AF_INET) - except error 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" +skip_if_py3 = pytest.mark.skipif(PY3, reason="Python 2 only") def join_bytes_or_unicode(prefix, suffix): @@ -199,12 +127,12 @@ 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 = socket() + port.bind(('', 0)) port.listen(1) - client = socket(port.family) + client = socket() client.setblocking(False) - client.connect_ex((loopback_address(port), port.getsockname()[1])) + client.connect_ex(("127.0.0.1", port.getsockname()[1])) client.setblocking(True) server = port.accept()[0] @@ -243,53 +171,47 @@ def _create_certificate_chain(): 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") + caext = X509Extension(b'basicConstraints', False, b'CA:true') # Step 1 cakey = PKey() - cakey.generate_key(TYPE_RSA, 2048) + cakey.generate_key(TYPE_RSA, 1024) 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.set_notAfter(b"20200101000000Z") cacert.add_extensions([caext]) cacert.set_serial_number(0) - cacert.sign(cakey, "sha256") + cacert.sign(cakey, "sha1") # Step 2 ikey = PKey() - ikey.generate_key(TYPE_RSA, 2048) + ikey.generate_key(TYPE_RSA, 1024) 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.set_notAfter(b"20200101000000Z") icert.add_extensions([caext]) icert.set_serial_number(0) - icert.sign(cakey, "sha256") + icert.sign(cakey, "sha1") # Step 3 skey = PKey() - skey.generate_key(TYPE_RSA, 2048) + skey.generate_key(TYPE_RSA, 1024) 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_notAfter(b"20200101000000Z") + scert.add_extensions([ + X509Extension(b'basicConstraints', True, b'CA:false')]) scert.set_serial_number(0) - scert.sign(ikey, "sha256") + scert.sign(ikey, "sha1") return [(cakey, cacert), (ikey, icert), (skey, scert)] @@ -346,10 +268,8 @@ def interact_in_memory(client_conn, server_conn): # 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), - ]: + 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: @@ -399,7 +319,6 @@ class TestVersion(object): 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 @@ -413,13 +332,8 @@ class TestVersion(object): number of version strings based on that indicator. """ versions = {} - for t in [ - SSLEAY_VERSION, - SSLEAY_CFLAGS, - SSLEAY_BUILT_ON, - SSLEAY_PLATFORM, - SSLEAY_DIR, - ]: + 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) @@ -432,29 +346,31 @@ 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, backend=default_backend() + public_exponent=65537, + key_size=2048, + backend=default_backend() ) public_key = key.public_key() builder = x509.CertificateBuilder() - builder = builder.subject_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"pyopenssl.org")]) - ) - builder = builder.issuer_name( - x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u"pyopenssl.org")]) - ) + builder = builder.subject_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"pyopenssl.org"), + ])) + builder = builder.issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"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, + x509.BasicConstraints(ca=True, path_length=None), critical=True, ) certificate = builder.sign( - private_key=key, algorithm=hashes.SHA256(), backend=default_backend() + private_key=key, algorithm=hashes.SHA256(), + backend=default_backend() ) ca_file = tmpdir.join("test.pem") @@ -470,20 +386,19 @@ def ca_file(tmpdir): @pytest.fixture def context(): """ - A simple "best TLS you can get" context. TLS 1.2+ in any reasonable OpenSSL + A simple TLS 1.0 context. """ - return Context(SSLv23_METHOD) + return Context(TLSv1_METHOD) class TestContext(object): """ Unit tests for `OpenSSL.SSL.Context`. """ - - @pytest.mark.parametrize( - "cipher_string", - [b"hello world:AES128-SHA", u"hello world:AES128-SHA"], - ) + @pytest.mark.parametrize("cipher_string", [ + b"hello world:AES128-SHA", + u"hello world:AES128-SHA", + ]) def test_set_cipher_list(self, context, cipher_string): """ `Context.set_cipher_list` accepts both byte and unicode strings @@ -495,32 +410,18 @@ class TestContext(object): assert "AES128-SHA" in conn.get_cipher_list() - def test_set_cipher_list_wrong_type(self, context): + @pytest.mark.parametrize("cipher_list,error", [ + (object(), TypeError), + ("imaginary-cipher", Error), + ]) + def test_set_cipher_list_wrong_args(self, context, cipher_list, error): """ `Context.set_cipher_list` raises `TypeError` when passed a non-string - argument. + argument and raises `OpenSSL.SSL.Error` when passed an incorrect cipher + list string. """ - with pytest.raises(TypeError): - context.set_cipher_list(object()) - - @flaky.flaky - 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 == ( - [ - ( - "SSL routines", - "SSL_CTX_set_cipher_list", - "no cipher match", - ) - ], - ) + with pytest.raises(error): + context.set_cipher_list(cipher_list) def test_load_client_ca(self, context, ca_file): """ @@ -544,7 +445,9 @@ class TestContext(object): """ Passing the path as unicode raises a warning but works. """ - pytest.deprecated_call(context.load_client_ca, ca_file.decode("ascii")) + pytest.deprecated_call( + context.load_client_ca, ca_file.decode("ascii") + ) def test_set_session_id(self, context): """ @@ -560,11 +463,9 @@ class TestContext(object): context.set_session_id(b"abc" * 1000) assert [ - ( - "SSL routines", - "SSL_CTX_set_session_id_context", - "ssl session id context too long", - ) + ("SSL routines", + "SSL_CTX_set_session_id_context", + "ssl session id context too long") ] == e.value.args[0] def test_set_session_id_unicode(self, context): @@ -598,19 +499,28 @@ class TestContext(object): with pytest.raises(ValueError): Context(10) + @skip_if_py3 + def test_method_long(self): + """ + On Python 2 `Context` accepts values of type `long` as well as `int`. + """ + Context(long(TLSv1_METHOD)) + def test_type(self): """ - `Context` can be used to create instances of that type. + `Context` and `ContextType` refer to the same type object and can + be used to create instances of that type. """ - assert is_consistent_type(Context, "Context", TLSv1_METHOD) + assert Context is ContextType + assert is_consistent_type(Context, 'Context', TLSv1_METHOD) def test_use_privatekey(self): """ `Context.use_privatekey` takes an `OpenSSL.crypto.PKey` instance. """ key = PKey() - key.generate_key(TYPE_RSA, 1024) - ctx = Context(SSLv23_METHOD) + key.generate_key(TYPE_RSA, 512) + ctx = Context(TLSv1_METHOD) ctx.use_privatekey(key) with pytest.raises(TypeError): ctx.use_privatekey("") @@ -620,7 +530,7 @@ class TestContext(object): `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` when passed the name of a file which does not exist. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) with pytest.raises(Error): ctx.use_privatekey_file(tmpfile) @@ -630,21 +540,23 @@ class TestContext(object): arguments does not raise an exception. """ key = PKey() - key.generate_key(TYPE_RSA, 1024) + key.generate_key(TYPE_RSA, 512) with open(pemfile, "wt") as pem: - pem.write(dump_privatekey(FILETYPE_PEM, key).decode("ascii")) + pem.write( + dump_privatekey(FILETYPE_PEM, key).decode("ascii") + ) - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) ctx.use_privatekey_file(pemfile, filetype) - @pytest.mark.parametrize("filetype", [object(), "", None, 1.0]) + @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) + ctx = Context(TLSv1_METHOD) with pytest.raises(TypeError): ctx.use_privatekey_file(tmpfile, filetype) @@ -668,12 +580,20 @@ class TestContext(object): FILETYPE_PEM, ) + @skip_if_py3 + def test_use_privatekey_file_long(self, tmpfile): + """ + On Python 2 `Context.use_privatekey_file` accepts a filetype of + type `long` as well as `int`. + """ + self._use_privatekey_file_test(tmpfile, long(FILETYPE_PEM)) + def test_use_certificate_wrong_args(self): """ `Context.use_certificate_wrong_args` raises `TypeError` when not passed exactly one `OpenSSL.crypto.X509` instance as an argument. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) with pytest.raises(TypeError): ctx.use_certificate("hello, world") @@ -683,7 +603,7 @@ class TestContext(object): `OpenSSL.crypto.X509` instance which has not been initialized (ie, which does not actually have any certificate data). """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) with pytest.raises(Error): ctx.use_certificate(X509()) @@ -696,15 +616,17 @@ class TestContext(object): # 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 = Context(SSLv23_METHOD) - ctx.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + ctx = Context(TLSv1_METHOD) + ctx.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM) + ) 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) + ctx = Context(TLSv1_METHOD) with pytest.raises(TypeError): ctx.use_certificate_file(object(), FILETYPE_PEM) with pytest.raises(TypeError): @@ -717,7 +639,7 @@ class TestContext(object): `Context.use_certificate_file` raises `OpenSSL.SSL.Error` if passed the name of a file which does not exist. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) with pytest.raises(Error): ctx.use_certificate_file(tmpfile) @@ -731,9 +653,9 @@ class TestContext(object): # 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) + pem_file.write(cleartextCertificatePEM) - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) ctx.use_certificate_file(certificate_file) def test_use_certificate_file_bytes(self, tmpfile): @@ -754,6 +676,19 @@ class TestContext(object): filename = tmpfile.decode(getfilesystemencoding()) + NON_ASCII self._use_certificate_file_test(filename) + @skip_if_py3 + def test_use_certificate_file_long(self, tmpfile): + """ + On Python 2 `Context.use_certificate_file` accepts a + filetype of type `long` as well as `int`. + """ + pem_filename = tmpfile + with open(pem_filename, "wb") as pem_file: + pem_file.write(cleartextCertificatePEM) + + ctx = Context(TLSv1_METHOD) + ctx.use_certificate_file(pem_filename, long(FILETYPE_PEM)) + def test_check_privatekey_valid(self): """ `Context.check_privatekey` returns `None` if the `Context` instance @@ -761,7 +696,7 @@ class TestContext(object): """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, client_cert_pem) - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.use_privatekey(key) context.use_certificate(cert) assert None is context.check_privatekey() @@ -774,7 +709,7 @@ class TestContext(object): """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.use_privatekey(key) context.use_certificate(cert) with pytest.raises(Error): @@ -786,7 +721,7 @@ class TestContext(object): using `Context.get_app_data`. """ app_data = object() - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_app_data(app_data) assert context.get_app_data() is app_data @@ -795,7 +730,7 @@ class TestContext(object): `Context.set_options` raises `TypeError` if called with a non-`int` argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_options(None) @@ -803,16 +738,26 @@ class TestContext(object): """ `Context.set_options` returns the new options value. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) options = context.set_options(OP_NO_SSLv2) assert options & OP_NO_SSLv2 == OP_NO_SSLv2 + @skip_if_py3 + def test_set_options_long(self): + """ + On Python 2 `Context.set_options` accepts values of type + `long` as well as `int`. + """ + context = Context(TLSv1_METHOD) + options = context.set_options(long(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) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_mode(None) @@ -821,15 +766,25 @@ class TestContext(object): `Context.set_mode` accepts a mode bitvector and returns the newly set mode. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) assert MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS) + @skip_if_py3 + def test_set_mode_long(self): + """ + On Python 2 `Context.set_mode` accepts values of type `long` as well + as `int`. + """ + context = Context(TLSv1_METHOD) + mode = context.set_mode(long(MODE_RELEASE_BUFFERS)) + assert MODE_RELEASE_BUFFERS & mode + def test_set_timeout_wrong_args(self): """ `Context.set_timeout` raises `TypeError` if called with a non-`int` argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_timeout(None) @@ -839,16 +794,26 @@ class TestContext(object): created using the context object. `Context.get_timeout` retrieves this value. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_timeout(1234) assert context.get_timeout() == 1234 + @skip_if_py3 + def test_timeout_long(self): + """ + On Python 2 `Context.set_timeout` accepts values of type `long` as + well as int. + """ + context = Context(TLSv1_METHOD) + context.set_timeout(long(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) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_verify_depth(None) @@ -858,20 +823,30 @@ class TestContext(object): a chain to follow before giving up. The value can be retrieved with `Context.get_verify_depth`. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_verify_depth(11) assert context.get_verify_depth() == 11 + @skip_if_py3 + def test_verify_depth_long(self): + """ + On Python 2 `Context.set_verify_depth` accepts values of type `long` + as well as int. + """ + context = Context(TLSv1_METHOD) + context.set_verify_depth(long(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) + key.generate_key(TYPE_RSA, 512) pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase) - with open(tmpfile, "w") as fObj: - fObj.write(pem.decode("ascii")) + with open(tmpfile, 'w') as fObj: + fObj.write(pem.decode('ascii')) return tmpfile def test_set_passwd_cb_wrong_args(self): @@ -879,7 +854,7 @@ class TestContext(object): `Context.set_passwd_cb` raises `TypeError` if called with a non-callable first argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_passwd_cb(None) @@ -895,8 +870,7 @@ class TestContext(object): def passphraseCallback(maxlen, verify, extra): calledWith.append((maxlen, verify, extra)) return passphrase - - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) context.use_privatekey_file(pemFile) assert len(calledWith) == 1 @@ -914,7 +888,7 @@ class TestContext(object): def passphraseCallback(maxlen, verify, extra): raise RuntimeError("Sorry, I am a fail.") - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(RuntimeError): context.use_privatekey_file(pemFile) @@ -929,7 +903,7 @@ class TestContext(object): def passphraseCallback(maxlen, verify, extra): return b"" - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(Error): context.use_privatekey_file(pemFile) @@ -944,7 +918,7 @@ class TestContext(object): def passphraseCallback(maxlen, verify, extra): return 10 - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) # TODO: Surely this is the wrong error? with pytest.raises(ValueError): @@ -963,7 +937,7 @@ class TestContext(object): assert maxlen == 1024 return passphrase + b"y" - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) # This shall succeed because the truncated result is the correct # passphrase. @@ -976,18 +950,19 @@ class TestContext(object): """ (server, client) = socket_pair() - clientSSL = Connection(Context(SSLv23_METHOD), client) + clientSSL = Connection(Context(TLSv1_METHOD), client) clientSSL.set_connect_state() called = [] def info(conn, where, ret): called.append((conn, where, ret)) - - context = Context(SSLv23_METHOD) + context = Context(TLSv1_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)) + context.use_certificate( + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + context.use_privatekey( + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverSSL = Connection(context, server) serverSSL.set_accept_state() @@ -1000,44 +975,10 @@ class TestContext(object): # 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) + conn for (conn, where, ret) in called + if not isinstance(conn, Connection)] + assert [] == notConnections, ( + "Some info callback arguments were not Connection instances.") def _load_verify_locations_test(self, *args): """ @@ -1047,25 +988,22 @@ class TestContext(object): """ (server, client) = socket_pair() - clientContext = Context(SSLv23_METHOD) + clientContext = Context(TLSv1_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, - ) + lambda conn, cert, errno, depth, preverify_ok: preverify_ok) clientSSL = Connection(clientContext, client) clientSSL.set_connect_state() - serverContext = Context(SSLv23_METHOD) + serverContext = Context(TLSv1_METHOD) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, root_cert_pem) - ) + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, root_key_pem) - ) + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverSSL = Connection(serverContext, server) serverSSL.set_accept_state() @@ -1077,7 +1015,7 @@ class TestContext(object): handshake(clientSSL, serverSSL) cert = clientSSL.get_peer_certificate() - assert cert.get_subject().CN == "Testing Root CA" + assert cert.get_subject().CN == 'Testing Root CA' def _load_verify_cafile(self, cafile): """ @@ -1086,8 +1024,8 @@ class TestContext(object): 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")) + with open(cafile, 'w') as fObj: + fObj.write(cleartextCertificatePEM.decode('ascii')) self._load_verify_locations_test(cafile) @@ -1113,7 +1051,7 @@ class TestContext(object): `Context.load_verify_locations` raises `Error` when passed a non-existent cafile. """ - clientContext = Context(SSLv23_METHOD) + clientContext = Context(TLSv1_METHOD) with pytest.raises(Error): clientContext.load_verify_locations(tmpfile) @@ -1128,10 +1066,10 @@ class TestContext(object): # 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"]: + 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")) + with open(cafile, 'w') as fObj: + fObj.write(cleartextCertificatePEM.decode('ascii')) self._load_verify_locations_test(None, capath) @@ -1158,7 +1096,7 @@ class TestContext(object): `Context.load_verify_locations` raises `TypeError` if with non-`str` arguments. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.load_verify_locations(object()) with pytest.raises(TypeError): @@ -1167,7 +1105,7 @@ class TestContext(object): @pytest.mark.skipif( not platform.startswith("linux"), reason="Loading fallback paths is a linux-specific behavior to " - "accommodate pyca/cryptography manylinux1 wheels", + "accommodate pyca/cryptography manylinux1 wheels" ) def test_fallback_default_verify_paths(self, monkeypatch): """ @@ -1178,19 +1116,19 @@ class TestContext(object): SSL_CTX_SET_default_verify_paths so that it can't find certs unless it loads via fallback. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) monkeypatch.setattr( _lib, "SSL_CTX_set_default_verify_paths", lambda x: 1 ) monkeypatch.setattr( SSL, "_CRYPTOGRAPHY_MANYLINUX1_CA_FILE", - _ffi.string(_lib.X509_get_default_cert_file()), + _ffi.string(_lib.X509_get_default_cert_file()) ) monkeypatch.setattr( SSL, "_CRYPTOGRAPHY_MANYLINUX1_CA_DIR", - _ffi.string(_lib.X509_get_default_cert_dir()), + _ffi.string(_lib.X509_get_default_cert_dir()) ) context.set_default_verify_paths() store = context.get_cert_store() @@ -1203,7 +1141,7 @@ class TestContext(object): """ Test that we return True/False appropriately if the env vars are set. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) dir_var = "CUSTOM_DIR_VAR" file_var = "CUSTOM_FILE_VAR" assert context._check_env_vars_set(dir_var, file_var) is False @@ -1216,13 +1154,13 @@ class TestContext(object): """ Test that we don't use the fallback path if env vars are set. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_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" - ) + 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") @@ -1231,14 +1169,16 @@ class TestContext(object): context.set_default_verify_paths() monkeypatch.setattr( - context, "_fallback_default_verify_paths", raiser(SystemError) + 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.", + "See LP#404343 and LP#404344." ) def test_set_default_verify_paths(self): """ @@ -1256,10 +1196,9 @@ class TestContext(object): context.set_default_verify_paths() context.set_verify( VERIFY_PEER, - lambda conn, cert, errno, depth, preverify_ok: preverify_ok, - ) + lambda conn, cert, errno, depth, preverify_ok: preverify_ok) - client = socket_any_family() + client = socket() client.connect(("encrypted.google.com", 443)) clientSSL = Connection(context, client) clientSSL.set_connect_state() @@ -1273,16 +1212,18 @@ class TestContext(object): Test that when passed empty arrays or paths that do not exist no errors are raised. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context._fallback_default_verify_paths([], []) - context._fallback_default_verify_paths(["/not/a/file"], ["/not/a/dir"]) + 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) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.add_extra_chain_cert(object()) @@ -1313,13 +1254,11 @@ class TestContext(object): The first argument passed to the verify callback is the `Connection` instance for which verification is taking place. """ - serverContext = Context(SSLv23_METHOD) + serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, root_key_pem) - ) + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, root_cert_pem) - ) + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) serverConnection = Connection(serverContext, None) class VerifyCallback(object): @@ -1328,7 +1267,7 @@ class TestContext(object): return 1 verify = VerifyCallback() - clientContext = Context(SSLv23_METHOD) + clientContext = Context(TLSv1_METHOD) clientContext.set_verify(VERIFY_PEER, verify.callback) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() @@ -1344,20 +1283,18 @@ class TestContext(object): 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 = Context(TLSv1_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, root_key_pem) - ) + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, root_cert_pem) - ) + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) 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 = Context(TLSv1_METHOD) clientContext.set_verify(VERIFY_PEER, verify_cb_get_subject) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() @@ -1372,17 +1309,14 @@ class TestContext(object): """ serverContext = Context(TLSv1_2_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, root_key_pem) - ) + load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, root_cert_pem) - ) + load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) 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: @@ -1390,74 +1324,6 @@ class TestContext(object): 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` @@ -1475,30 +1341,29 @@ class TestContext(object): # 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")) + 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 = Context(TLSv1_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 = Context(TLSv1_METHOD) clientContext.set_verify( - VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb - ) + VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) clientContext.load_verify_locations(str(tmpdir.join("ca.pem"))) # Try it out. @@ -1522,23 +1387,22 @@ class TestContext(object): caFile = join_bytes_or_unicode(certdir, "ca.pem") # Write out the chain file. - with open(chainFile, "wb") as fObj: + 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")) + with open(caFile, 'w') as fObj: + fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii')) - serverContext = Context(SSLv23_METHOD) + serverContext = Context(TLSv1_METHOD) serverContext.use_certificate_chain_file(chainFile) serverContext.use_privatekey(skey) - clientContext = Context(SSLv23_METHOD) + clientContext = Context(TLSv1_METHOD) clientContext.set_verify( - VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb - ) + VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) clientContext.load_verify_locations(caFile) self._handshake_test(serverContext, clientContext) @@ -1568,7 +1432,7 @@ class TestContext(object): `Context.use_certificate_chain_file` raises `TypeError` if passed a non-byte string single argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.use_certificate_chain_file(object()) @@ -1578,7 +1442,7 @@ class TestContext(object): passed a bad chain file name (for example, the name of a file which does not exist). """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(Error): context.use_certificate_chain_file(tmpfile) @@ -1587,28 +1451,42 @@ class TestContext(object): `Context.get_verify_mode` returns the verify mode flags previously passed to `Context.set_verify`. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) assert context.get_verify_mode() == 0 - context.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE) + context.set_verify( + VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) assert context.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE) - @pytest.mark.parametrize("mode", [None, 1.0, object(), "mode"]) + @skip_if_py3 + def test_set_verify_mode_long(self): + """ + On Python 2 `Context.set_verify_mode` accepts values of type `long` + as well as `int`. + """ + context = Context(TLSv1_METHOD) + assert context.get_verify_mode() == 0 + context.set_verify( + long(VERIFY_PEER | VERIFY_CLIENT_ONCE), lambda *args: None + ) # pragma: nocover + 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) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): - context.set_verify(mode=mode) + context.set_verify(mode=mode, callback=lambda *args: None) - @pytest.mark.parametrize("callback", [1.0, "mode", ("foo", "bar")]) + @pytest.mark.parametrize('callback', [None, 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) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_verify(mode=VERIFY_PEER, callback=callback) @@ -1617,7 +1495,7 @@ class TestContext(object): `Context.load_tmp_dh` raises `TypeError` if called with a non-`str` argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.load_tmp_dh(object()) @@ -1626,7 +1504,7 @@ class TestContext(object): `Context.load_tmp_dh` raises `OpenSSL.SSL.Error` if the specified file does not exist. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(Error): context.load_tmp_dh(b"hello") @@ -1635,11 +1513,12 @@ class TestContext(object): Verify that calling ``Context.load_tmp_dh`` with the given filename does not raise an exception. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with open(dhfilename, "w") as dhfile: dhfile.write(dhparam) context.load_tmp_dh(dhfilename) + # XXX What should I assert here? -exarkun def test_load_tmp_dh_bytes(self, tmpfile): """ @@ -1664,7 +1543,7 @@ class TestContext(object): `Context.set_tmp_ecdh` sets the elliptic curve for Diffie-Hellman to the specified curve. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) for curve in get_elliptic_curves(): if curve.name.startswith(u"Oakley-"): # Setting Oakley-EC2N-4 and Oakley-EC2N-3 adds @@ -1681,7 +1560,7 @@ class TestContext(object): a non-integer argument. called with other than one integer argument. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): context.set_session_cache_mode(object()) @@ -1690,17 +1569,27 @@ class TestContext(object): `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 = Context(TLSv1_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() + @skip_if_py3 + def test_session_cache_mode_long(self): + """ + On Python 2 `Context.set_session_cache_mode` accepts values + of type `long` as well as `int`. + """ + context = Context(TLSv1_METHOD) + context.set_session_cache_mode(long(SESS_CACHE_BOTH)) + 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) + context = Context(TLSv1_METHOD) store = context.get_cert_store() assert isinstance(store, X509Store) @@ -1710,9 +1599,9 @@ class TestContext(object): It raises a TypeError if the list of profiles is not a byte string. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(TypeError): - context.set_tlsext_use_srtp(text_type("SRTP_AES128_CM_SHA1_80")) + context.set_tlsext_use_srtp(text_type('SRTP_AES128_CM_SHA1_80')) def test_set_tlsext_use_srtp_invalid_profile(self): """ @@ -1720,9 +1609,9 @@ class TestContext(object): It raises an Error if the call to OpenSSL fails. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) with pytest.raises(Error): - context.set_tlsext_use_srtp(b"SRTP_BOGUS") + context.set_tlsext_use_srtp(b'SRTP_BOGUS') def test_set_tlsext_use_srtp_valid(self): """ @@ -1730,8 +1619,8 @@ class TestContext(object): It does not return anything. """ - context = Context(SSLv23_METHOD) - assert context.set_tlsext_use_srtp(b"SRTP_AES128_CM_SHA1_80") is None + context = Context(TLSv1_METHOD) + assert context.set_tlsext_use_srtp(b'SRTP_AES128_CM_SHA1_80') is None class TestServerNameCallback(object): @@ -1739,20 +1628,18 @@ class TestServerNameCallback(object): 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 = Context(TLSv1_METHOD) context.set_tlsext_servername_callback(callback) tracker = ref(callback) @@ -1783,8 +1670,7 @@ class TestServerNameCallback(object): def servername(conn): args.append((conn, conn.get_servername())) - - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.set_tlsext_servername_callback(servername) # Lose our reference to it. The Context is responsible for keeping it @@ -1795,14 +1681,13 @@ class TestServerNameCallback(object): # 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) - ) + 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 = Connection(Context(TLSv1_METHOD), None) client.set_connect_state() interact_in_memory(server, client) @@ -1820,21 +1705,19 @@ class TestServerNameCallback(object): def servername(conn): args.append((conn, conn.get_servername())) - - context = Context(SSLv23_METHOD) + context = Context(TLSv1_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) - ) + 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 = Connection(Context(TLSv1_METHOD), None) client.set_connect_state() client.set_tlsext_host_name(b"foo1.example.com") @@ -1843,36 +1726,38 @@ class TestServerNameCallback(object): assert args == [(server, b"foo1.example.com")] -class TestApplicationLayerProtoNegotiation(object): +class TestNextProtoNegotiation(object): """ - Tests for ALPN in PyOpenSSL. + Test for Next Protocol Negotiation in PyOpenSSL. """ - - def test_alpn_success(self): + def test_npn_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. + Tests that clients and servers that agree on the negotiated next + protocol can correct establish a connection, and that the agreed + protocol is reported by the connections. """ + advertise_args = [] select_args = [] + def advertise(conn): + advertise_args.append((conn,)) + return [b'http/1.1', b'spdy/2'] + def select(conn, options): select_args.append((conn, options)) - return b"spdy/2" + return b'spdy/2' - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) + server_context = Context(TLSv1_METHOD) + server_context.set_npn_advertise_callback(advertise) - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(select) + client_context = Context(TLSv1_METHOD) + client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) @@ -1883,76 +1768,79 @@ class TestApplicationLayerProtoNegotiation(object): interact_in_memory(server, client) - assert select_args == [(server, [b"http/1.1", b"spdy/2"])] + assert advertise_args == [(server,)] + assert select_args == [(client, [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" + assert server.get_next_proto_negotiated() == b'spdy/2' + assert client.get_next_proto_negotiated() == b'spdy/2' - def test_alpn_set_on_connection(self): + def test_npn_client_fail(self): """ - The same as test_alpn_success, but setting the ALPN protocols on - the connection rather than the context. + Tests that when clients and servers cannot agree on what protocol + to use next that the TLS connection does not get established. """ + advertise_args = [] select_args = [] + def advertise(conn): + advertise_args.append((conn,)) + return [b'http/1.1', b'spdy/2'] + def select(conn, options): select_args.append((conn, options)) - return b"spdy/2" + return b'' - # Setup the client context but don't set any ALPN protocols. - client_context = Context(SSLv23_METHOD) + server_context = Context(TLSv1_METHOD) + server_context.set_npn_advertise_callback(advertise) - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(select) + client_context = Context(TLSv1_METHOD) + client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + 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"])] + # If the client doesn't return anything, the connection will fail. + with pytest.raises(Error): + interact_in_memory(server, client) - assert server.get_alpn_proto_negotiated() == b"spdy/2" - assert client.get_alpn_proto_negotiated() == b"spdy/2" + assert advertise_args == [(server,)] + assert select_args == [(client, [b'http/1.1', b'spdy/2'])] - def test_alpn_server_fail(self): + def test_npn_select_error(self): """ - When clients and servers cannot agree on what protocol to use next - the TLS connection does not get established. + Test that we can handle exceptions in the select callback. If + select fails it should be fatal to the connection. """ - select_args = [] + advertise_args = [] + + def advertise(conn): + advertise_args.append((conn,)) + return [b'http/1.1', b'spdy/2'] def select(conn, options): - select_args.append((conn, options)) - return b"" + raise TypeError - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) + server_context = Context(TLSv1_METHOD) + server_context.set_npn_advertise_callback(advertise) - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(select) + client_context = Context(TLSv1_METHOD) + client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) @@ -1961,37 +1849,39 @@ class TestApplicationLayerProtoNegotiation(object): client = Connection(client_context, None) client.set_connect_state() - # If the client doesn't return anything, the connection will fail. - with pytest.raises(Error): + # If the callback throws an exception it should be raised here. + with pytest.raises(TypeError): interact_in_memory(server, client) + assert advertise_args == [(server,), ] - assert select_args == [(server, [b"http/1.1", b"spdy/2"])] - - def test_alpn_no_server_overlap(self): + def test_npn_advertise_error(self): """ - A server can allow a TLS handshake to complete without - agreeing to an application protocol by returning - ``NO_OVERLAPPING_PROTOCOLS``. + Test that we can handle exceptions in the advertise callback. If + advertise fails no NPN is advertised to the client. """ - refusal_args = [] + select_args = [] - def refusal(conn, options): - refusal_args.append((conn, options)) - return NO_OVERLAPPING_PROTOCOLS + def advertise(conn): + raise TypeError - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) + def select(conn, options): # pragma: nocover + """ + Assert later that no args are actually appended. + """ + select_args.append((conn, options)) + return b'' - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(refusal) + server_context = Context(TLSv1_METHOD) + server_context.set_npn_advertise_callback(advertise) + + client_context = Context(TLSv1_METHOD) + client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) @@ -2000,125 +1890,216 @@ class TestApplicationLayerProtoNegotiation(object): client = Connection(client_context, None) client.set_connect_state() - # Do the dance. - interact_in_memory(server, client) + # If the client doesn't return anything, the connection will fail. + with pytest.raises(TypeError): + interact_in_memory(server, client) + assert select_args == [] - assert refusal_args == [(server, [b"http/1.1", b"spdy/2"])] - assert client.get_alpn_proto_negotiated() == b"" +class TestApplicationLayerProtoNegotiation(object): + """ + Tests for ALPN in PyOpenSSL. + """ + # Skip tests on versions that don't support ALPN. + if _lib.Cryptography_HAS_ALPN: + + 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(TLSv1_METHOD) + client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) + + server_context = Context(TLSv1_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() - 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 = [] + client = Connection(client_context, None) + client.set_connect_state() - def invalid_cb(conn, options): - invalid_cb_args.append((conn, options)) - return u"can't return unicode" + interact_in_memory(server, client) - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) + assert select_args == [(server, [b'http/1.1', b'spdy/2'])] - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(invalid_cb) + assert server.get_alpn_proto_negotiated() == b'spdy/2' + assert client.get_alpn_proto_negotiated() == b'spdy/2' - # 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) - ) + 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 = [] - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() + def select(conn, options): + select_args.append((conn, options)) + return b'spdy/2' - client = Connection(client_context, None) - client.set_connect_state() + # Setup the client context but don't set any ALPN protocols. + client_context = Context(TLSv1_METHOD) + + server_context = Context(TLSv1_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() - # 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 select_args == [(server, [b'http/1.1', b'spdy/2'])] - assert client.get_alpn_proto_negotiated() == b"" + assert server.get_alpn_proto_negotiated() == b'spdy/2' + assert client.get_alpn_proto_negotiated() == b'spdy/2' - 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"]) + 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 = [] - server_context = Context(SSLv23_METHOD) + def select(conn, options): + select_args.append((conn, options)) + return b'' - # 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) - ) + client_context = Context(TLSv1_METHOD) + client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() + server_context = Context(TLSv1_METHOD) + server_context.set_alpn_select_callback(select) - client = Connection(client_context, None) - client.set_connect_state() + # 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 the dance. - interact_in_memory(server, client) + # Do a little connection to trigger the logic + server = Connection(server_context, None) + server.set_accept_state() - assert client.get_alpn_proto_negotiated() == b"" + client = Connection(client_context, None) + client.set_connect_state() - def test_alpn_callback_exception(self): - """ - We can handle exceptions in the ALPN select callback. - """ - select_args = [] + # If the client doesn't return anything, the connection will fail. + with pytest.raises(Error): + interact_in_memory(server, client) - def select(conn, options): - select_args.append((conn, options)) - raise TypeError() + assert select_args == [(server, [b'http/1.1', b'spdy/2'])] - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) + 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(TLSv1_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) + server_context = Context(TLSv1_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) - ) + # 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() + # 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() + client = Connection(client_context, None) + client.set_connect_state() - with pytest.raises(TypeError): + # Do the dance. interact_in_memory(server, client) - assert select_args == [(server, [b"http/1.1", b"spdy/2"])] + + 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(TLSv1_METHOD) + client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) + + server_context = Context(TLSv1_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'])] + + else: + # No ALPN. + def test_alpn_not_implemented(self): + """ + If ALPN is not in OpenSSL, we should raise NotImplementedError. + """ + # Test the context methods first. + context = Context(TLSv1_METHOD) + with pytest.raises(NotImplementedError): + context.set_alpn_protos(None) + with pytest.raises(NotImplementedError): + context.set_alpn_select_callback(None) + + # Now test a connection. + conn = Connection(context) + with pytest.raises(NotImplementedError): + conn.set_alpn_protos(None) class TestSession(object): """ Unit tests for :py:obj:`OpenSSL.SSL.Session`. """ - def test_construction(self): """ :py:class:`Session` can be constructed with no arguments, creating @@ -2132,7 +2113,6 @@ class TestConnection(object): """ Unit tests for `OpenSSL.SSL.Connection`. """ - # XXX get_peer_certificate -> None # XXX sock_shutdown # XXX master_key -> TypeError @@ -2149,12 +2129,14 @@ class TestConnection(object): def test_type(self): """ - `Connection` can be used to create instances of that type. + `Connection` and `ConnectionType` refer to the same type object and + can be used to create instances of that type. """ - ctx = Context(SSLv23_METHOD) - assert is_consistent_type(Connection, "Connection", ctx, None) + assert Connection is ConnectionType + ctx = Context(TLSv1_METHOD) + assert is_consistent_type(Connection, 'Connection', ctx, None) - @pytest.mark.parametrize("bad_context", [object(), "context", None, 1]) + @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` @@ -2163,35 +2145,12 @@ class TestConnection(object): 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(u"deprecated") - def test_get_context(self): """ `Connection.get_context` returns the `Context` instance used to construct the `Connection` instance. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) connection = Connection(context, None) assert connection.get_context() is context @@ -2200,7 +2159,7 @@ class TestConnection(object): `Connection.set_context` raises `TypeError` if called with a non-`Context` instance argument. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_context(object()) @@ -2216,7 +2175,7 @@ class TestConnection(object): used for the connection. """ original = Context(SSLv23_METHOD) - replacement = Context(SSLv23_METHOD) + replacement = Context(TLSv1_METHOD) connection = Connection(original, None) connection.set_context(replacement) assert replacement is connection.get_context() @@ -2231,13 +2190,13 @@ class TestConnection(object): 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) + conn = Connection(Context(TLSv1_METHOD), None) with pytest.raises(TypeError): conn.set_tlsext_host_name(object()) with pytest.raises(TypeError): conn.set_tlsext_host_name(b"with\0null") - if not PY2: + if PY3: # On Python 3.x, don't accidentally implicitly convert from text. with pytest.raises(TypeError): conn.set_tlsext_host_name(b"example.com".decode("ascii")) @@ -2247,7 +2206,7 @@ class TestConnection(object): `Connection.pending` returns the number of bytes available for immediate read. """ - connection = Connection(Context(SSLv23_METHOD), None) + connection = Connection(Context(TLSv1_METHOD), None) assert connection.pending() == 0 def test_peek(self): @@ -2256,17 +2215,17 @@ class TestConnection(object): 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" + 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()) + connection = Connection(Context(TLSv1_METHOD), socket()) with pytest.raises(TypeError): connection.connect(None) @@ -2275,13 +2234,13 @@ class TestConnection(object): `Connection.connect` raises `socket.error` if the underlying socket connect method raises it. """ - client = socket_any_family() - context = Context(SSLv23_METHOD) + client = socket() + context = Context(TLSv1_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)) + clientSSL.connect(("127.0.0.1", 1)) except error as e: exc = e assert exc.args[0] == ECONNREFUSED @@ -2290,28 +2249,28 @@ class TestConnection(object): """ `Connection.connect` establishes a connection to the specified address. """ - port = socket_any_family() - port.bind(("", 0)) + port = socket() + port.bind(('', 0)) port.listen(3) - clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) - clientSSL.connect((loopback_address(port), port.getsockname()[1])) + clientSSL = Connection(Context(TLSv1_METHOD), socket()) + clientSSL.connect(('127.0.0.1', port.getsockname()[1])) # XXX An assertion? Or something? @pytest.mark.skipif( platform == "darwin", - reason="connect_ex sometimes causes a kernel panic on OS X 10.6.4", + reason="connect_ex sometimes causes a kernel panic on OS X 10.6.4" ) 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 = socket() + port.bind(('', 0)) port.listen(3) - clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) + clientSSL = Connection(Context(TLSv1_METHOD), socket()) clientSSL.setblocking(False) result = clientSSL.connect_ex(port.getsockname()) expected = (EINPROGRESS, EWOULDBLOCK) @@ -2323,19 +2282,19 @@ class TestConnection(object): tuple of a new `Connection` (the accepted client) and the address the connection originated from. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_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() + port = socket() portSSL = Connection(ctx, port) - portSSL.bind(("", 0)) + portSSL.bind(('', 0)) portSSL.listen(3) - clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) + clientSSL = Connection(Context(TLSv1_METHOD), socket()) # 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])) + clientSSL.connect(('127.0.0.1', portSSL.getsockname()[1])) serverSSL, address = portSSL.accept() @@ -2348,7 +2307,7 @@ class TestConnection(object): `Connection.set_shutdown` raises `TypeError` if called with arguments other than integers. """ - connection = Connection(Context(SSLv23_METHOD), None) + connection = Connection(Context(TLSv1_METHOD), None) with pytest.raises(TypeError): connection.set_shutdown(None) @@ -2387,14 +2346,12 @@ class TestConnection(object): If the underlying connection is truncated, `Connection.shutdown` raises an `Error`. """ - server_ctx = Context(SSLv23_METHOD) - client_ctx = Context(SSLv23_METHOD) + server_ctx = Context(TLSv1_METHOD) + client_ctx = Context(TLSv1_METHOD) server_ctx.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_ctx.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(server_ctx, None) client = Connection(client_ctx, None) handshake_in_memory(client, server) @@ -2410,10 +2367,20 @@ class TestConnection(object): `Connection.set_shutdown` sets the state of the SSL connection shutdown process. """ - connection = Connection(Context(SSLv23_METHOD), socket_any_family()) + connection = Connection(Context(TLSv1_METHOD), socket()) connection.set_shutdown(RECEIVED_SHUTDOWN) assert connection.get_shutdown() == RECEIVED_SHUTDOWN + @skip_if_py3 + def test_set_shutdown_long(self): + """ + On Python 2 `Connection.set_shutdown` accepts an argument + of type `long` as well as `int`. + """ + connection = Connection(Context(TLSv1_METHOD), socket()) + connection.set_shutdown(long(RECEIVED_SHUTDOWN)) + assert connection.get_shutdown() == RECEIVED_SHUTDOWN + def test_state_string(self): """ `Connection.state_string` verbosely describes the current state of @@ -2424,12 +2391,10 @@ class TestConnection(object): client = loopback_client_factory(client) assert server.get_state_string() in [ - b"before/accept initialization", - b"before SSL initialization", + b"before/accept initialization", b"before SSL initialization" ] assert client.get_state_string() in [ - b"before/connect initialization", - b"before SSL initialization", + b"before/connect initialization", b"before SSL initialization" ] def test_app_data(self): @@ -2438,7 +2403,7 @@ class TestConnection(object): `Connection.set_app_data` and later retrieved with `Connection.get_app_data`. """ - conn = Connection(Context(SSLv23_METHOD), None) + conn = Connection(Context(TLSv1_METHOD), None) assert None is conn.get_app_data() app_data = object() conn.set_app_data(app_data) @@ -2449,7 +2414,7 @@ class TestConnection(object): `Connection.makefile` is not implemented and calling that method raises `NotImplementedError`. """ - conn = Connection(Context(SSLv23_METHOD), None) + conn = Connection(Context(TLSv1_METHOD), None) with pytest.raises(NotImplementedError): conn.makefile() @@ -2460,7 +2425,7 @@ class TestConnection(object): chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) context.use_certificate(scert) client = Connection(context, None) cert = client.get_certificate() @@ -2473,7 +2438,7 @@ class TestConnection(object): If there is no certificate, it returns None. """ - context = Context(SSLv23_METHOD) + context = Context(TLSv1_METHOD) client = Connection(context, None) cert = client.get_certificate() assert cert is None @@ -2486,7 +2451,7 @@ class TestConnection(object): chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - serverContext = Context(SSLv23_METHOD) + serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) serverContext.add_extra_chain_cert(icert) @@ -2495,7 +2460,7 @@ class TestConnection(object): server.set_accept_state() # Create the client - clientContext = Context(SSLv23_METHOD) + clientContext = Context(TLSv1_METHOD) clientContext.set_verify(VERIFY_NONE, verify_cb) client = Connection(clientContext, None) client.set_connect_state() @@ -2513,79 +2478,22 @@ class TestConnection(object): `Connection.get_peer_cert_chain` returns `None` if the peer sends no certificate chain. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_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 = Connection(Context(TLSv1_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_get_session_unconnected(self): """ `Connection.get_session` returns `None` when used with an object which has not been connected. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) server = Connection(ctx, None) session = server.get_session() assert None is session @@ -2614,7 +2522,7 @@ class TestConnection(object): `Connection.set_session` raises `TypeError` if called with an object that is not an instance of `Session`. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_session(123) @@ -2641,17 +2549,17 @@ class TestConnection(object): server.set_accept_state() return server - originalServer, originalClient = loopback(server_factory=makeServer) + 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 - ) + 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 @@ -2667,15 +2575,24 @@ class TestConnection(object): 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 + # Make this work on both OpenSSL 1.0.0, which doesn't support TLSv1.2 + # and also on OpenSSL 1.1.0 which doesn't support SSLv3. (SSL_ST_INIT + # is a way to check for 1.1.0) + if SSL_ST_INIT is None: + v1 = TLSv1_2_METHOD + v2 = TLSv1_METHOD + elif hasattr(_lib, "SSLv3_method"): + v1 = TLSv1_METHOD + v2 = SSLv3_METHOD + else: + pytest.skip("Test requires either OpenSSL 1.1.0 or SSLv3") 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") + ctx.set_session_id("unity-test") def makeServer(socket): server = Connection(ctx, socket) @@ -2688,8 +2605,7 @@ class TestConnection(object): return client originalServer, originalClient = loopback( - server_factory=makeServer, client_factory=makeOriginalClient - ) + server_factory=makeServer, client_factory=makeOriginalClient) originalSession = originalClient.get_session() def makeClient(socket): @@ -2725,10 +2641,9 @@ class TestConnection(object): raise else: pytest.fail( - "Failed to fill socket buffer, cannot test BIO want write" - ) + "Failed to fill socket buffer, cannot test BIO want write") - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, client_socket) # Client's speak first, so make it an SSL client conn.set_connect_state() @@ -2742,7 +2657,7 @@ class TestConnection(object): `Connection.get_finished` returns `None` before TLS handshake is completed. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) assert connection.get_finished() is None @@ -2751,7 +2666,7 @@ class TestConnection(object): `Connection.get_peer_finished` returns `None` before TLS handshake is completed. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) assert connection.get_peer_finished() is None @@ -2795,7 +2710,7 @@ class TestConnection(object): `Connection.get_cipher_name` returns `None` if no connection has been established. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_name() is None @@ -2805,10 +2720,8 @@ class TestConnection(object): name of the currently used cipher. """ server, client = loopback() - server_cipher_name, client_cipher_name = ( - server.get_cipher_name(), - client.get_cipher_name(), - ) + server_cipher_name, client_cipher_name = \ + server.get_cipher_name(), client.get_cipher_name() assert isinstance(server_cipher_name, text_type) assert isinstance(client_cipher_name, text_type) @@ -2820,7 +2733,7 @@ class TestConnection(object): `Connection.get_cipher_version` returns `None` if no connection has been established. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_version() is None @@ -2830,10 +2743,8 @@ class TestConnection(object): 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(), - ) + server_cipher_version, client_cipher_version = \ + server.get_cipher_version(), client.get_cipher_version() assert isinstance(server_cipher_version, text_type) assert isinstance(client_cipher_version, text_type) @@ -2845,7 +2756,7 @@ class TestConnection(object): `Connection.get_cipher_bits` returns `None` if no connection has been established. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_bits() is None @@ -2855,10 +2766,8 @@ class TestConnection(object): of the currently used cipher. """ server, client = loopback() - server_cipher_bits, client_cipher_bits = ( - server.get_cipher_bits(), - client.get_cipher_bits(), - ) + 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) @@ -2898,18 +2807,18 @@ class TestConnection(object): `Connection.bio_read` raises `OpenSSL.SSL.WantReadError` if there are no bytes available to be read from the BIO. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) with pytest.raises(WantReadError): conn.bio_read(1024) - @pytest.mark.parametrize("bufsize", [1.0, None, object(), "bufsize"]) + @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) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) with pytest.raises(TypeError): conn.bio_read(bufsize) @@ -2919,7 +2828,7 @@ class TestConnection(object): `Connection.bio_read` accepts an integer giving the maximum number of bytes to read and return. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) conn.set_connect_state() try: @@ -2929,18 +2838,33 @@ class TestConnection(object): data = conn.bio_read(2) assert 2 == len(data) + @skip_if_py3 + def test_buffer_size_long(self): + """ + On Python 2 `Connection.bio_read` accepts values of type `long` as + well as `int`. + """ + ctx = Context(TLSv1_METHOD) + conn = Connection(ctx, None) + conn.set_connect_state() + try: + conn.do_handshake() + except WantReadError: + pass + data = conn.bio_read(long(2)) + assert 2 == len(data) + class TestConnectionGetCipherList(object): """ 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) + connection = Connection(Context(TLSv1_METHOD), None) ciphers = connection.get_cipher_list() assert isinstance(ciphers, list) for cipher in ciphers: @@ -2951,26 +2875,22 @@ class VeryLarge(bytes): """ Mock object so that we don't have to allocate 2**31 bytes """ - def __len__(self): - return 2 ** 31 + return 2**31 class TestConnectionSend(object): """ 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) + connection = Connection(Context(TLSv1_METHOD), None) with pytest.raises(TypeError): connection.send(object()) - with pytest.raises(TypeError): - connection.send([1, 2, 3]) def test_short_bytes(self): """ @@ -2978,9 +2898,9 @@ class TestConnectionSend(object): and returns the number of bytes sent. """ server, client = loopback() - count = server.send(b"xy") + count = server.send(b'xy') assert count == 2 - assert client.recv(2) == b"xy" + assert client.recv(2) == b'xy' def test_text(self): """ @@ -2991,11 +2911,12 @@ class TestConnectionSend(object): with pytest.warns(DeprecationWarning) as w: simplefilter("always") count = server.send(b"xy".decode("ascii")) - assert "{0} for buf is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message) + assert ( + "{0} for buf is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) assert count == 2 - assert client.recv(2) == b"xy" + assert client.recv(2) == b'xy' def test_short_memoryview(self): """ @@ -3004,19 +2925,9 @@ class TestConnectionSend(object): 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")) + count = server.send(memoryview(b'xy')) assert count == 2 - assert client.recv(2) == b"xy" + assert client.recv(2) == b'xy' @skip_if_py3 def test_short_buffer(self): @@ -3026,13 +2937,13 @@ class TestConnectionSend(object): of bytes sent. """ server, client = loopback() - count = server.send(buffer(b"xy")) # noqa: F821 + count = server.send(buffer(b'xy')) assert count == 2 - assert client.recv(2) == b"xy" + assert client.recv(2) == b'xy' @pytest.mark.skipif( - sys.maxsize < 2 ** 31, - reason="sys.maxsize < 2**31 - test requires 64 bit", + sys.maxsize < 2**31, + reason="sys.maxsize < 2**31 - test requires 64 bit" ) def test_buf_too_large(self): """ @@ -3040,7 +2951,7 @@ class TestConnectionSend(object): `Connection.send` bails out as SSL_write only accepts an int for the buffer length. """ - connection = Connection(Context(SSLv23_METHOD), None) + connection = Connection(Context(TLSv1_METHOD), None) with pytest.raises(ValueError) as exc_info: connection.send(VeryLarge()) exc_info.match(r"Cannot send more than .+ bytes at once") @@ -3058,7 +2969,6 @@ class TestConnectionRecvInto(object): """ Tests for `Connection.recv_into`. """ - def _no_length_test(self, factory): """ Assert that when the given buffer is passed to `Connection.recv_into`, @@ -3068,10 +2978,10 @@ class TestConnectionRecvInto(object): output_buffer = factory(5) server, client = loopback() - server.send(b"xy") + server.send(b'xy') assert client.recv_into(output_buffer) == 2 - assert output_buffer == bytearray(b"xy\x00\x00\x00") + assert output_buffer == bytearray(b'xy\x00\x00\x00') def test_bytearray_no_length(self): """ @@ -3089,10 +2999,10 @@ class TestConnectionRecvInto(object): output_buffer = factory(10) server, client = loopback() - server.send(b"abcdefghij") + server.send(b'abcdefghij') assert client.recv_into(output_buffer, 5) == 5 - assert output_buffer == bytearray(b"abcde\x00\x00\x00\x00\x00") + assert output_buffer == bytearray(b'abcde\x00\x00\x00\x00\x00') def test_bytearray_respects_length(self): """ @@ -3111,12 +3021,12 @@ class TestConnectionRecvInto(object): output_buffer = factory(5) server, client = loopback() - server.send(b"abcdefghij") + server.send(b'abcdefghij') assert client.recv_into(output_buffer) == 5 - assert output_buffer == bytearray(b"abcde") + assert output_buffer == bytearray(b'abcde') rest = client.recv(5) - assert b"fghij" == rest + assert b'fghij' == rest def test_bytearray_doesnt_overfill(self): """ @@ -3137,12 +3047,12 @@ class TestConnectionRecvInto(object): def test_peek(self): server, client = loopback() - server.send(b"xy") + 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") + assert output_buffer == bytearray(b'xy\x00\x00\x00') def test_memoryview_no_length(self): """ @@ -3181,17 +3091,14 @@ class TestConnectionSendall(object): """ 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) + connection = Connection(Context(TLSv1_METHOD), None) with pytest.raises(TypeError): connection.sendall(object()) - with pytest.raises(TypeError): - connection.sendall([1, 2, 3]) def test_short(self): """ @@ -3199,8 +3106,8 @@ class TestConnectionSendall(object): passed to it. """ server, client = loopback() - server.sendall(b"x") - assert client.recv(1) == b"x" + server.sendall(b'x') + assert client.recv(1) == b'x' def test_text(self): """ @@ -3211,9 +3118,10 @@ class TestConnectionSendall(object): with pytest.warns(DeprecationWarning) as w: simplefilter("always") server.sendall(b"x".decode("ascii")) - assert "{0} for buf is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message) + assert ( + "{0} for buf is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) == str(w[-1].message)) assert client.recv(1) == b"x" def test_short_memoryview(self): @@ -3222,8 +3130,8 @@ class TestConnectionSendall(object): `Connection.sendall` transmits all of them. """ server, client = loopback() - server.sendall(memoryview(b"x")) - assert client.recv(1) == b"x" + server.sendall(memoryview(b'x')) + assert client.recv(1) == b'x' @skip_if_py3 def test_short_buffers(self): @@ -3232,9 +3140,8 @@ class TestConnectionSendall(object): `Connection.sendall` transmits all of them. """ server, client = loopback() - count = server.sendall(buffer(b"xy")) # noqa: F821 - assert count == 2 - assert client.recv(2) == b"xy" + server.sendall(buffer(b'x')) + assert client.recv(1) == b'x' def test_long(self): """ @@ -3245,7 +3152,7 @@ class TestConnectionSendall(object): # 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" + message = b'x' * (1024 * 32 - 1) + b'y' server.sendall(message) accum = [] received = 0 @@ -3253,7 +3160,7 @@ class TestConnectionSendall(object): data = client.recv(1024) accum.append(data) received += len(data) - assert message == b"".join(accum) + assert message == b''.join(accum) def test_closed(self): """ @@ -3274,13 +3181,12 @@ class TestConnectionRenegotiate(object): """ 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) + connection = Connection(Context(TLSv1_METHOD), None) assert connection.total_renegotiations() == 0 def test_renegotiate(self): @@ -3318,13 +3224,12 @@ class TestError(object): """ Unit tests for `OpenSSL.SSL.Error`. """ - def test_type(self): """ `Error` is an exception type. """ assert issubclass(Error, Exception) - assert Error.__name__ == "Error" + assert Error.__name__ == 'Error' class TestConstants(object): @@ -3335,10 +3240,9 @@ class TestConstants(object): 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", + reason="OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old" ) def test_op_no_query_mtu(self): """ @@ -3350,7 +3254,7 @@ class TestConstants(object): @pytest.mark.skipif( OP_COOKIE_EXCHANGE is None, reason="OP_COOKIE_EXCHANGE unavailable - " - "OpenSSL version may be too old", + "OpenSSL version may be too old" ) def test_op_cookie_exchange(self): """ @@ -3361,7 +3265,7 @@ class TestConstants(object): @pytest.mark.skipif( OP_NO_TICKET is None, - reason="OP_NO_TICKET unavailable - OpenSSL version may be too old", + reason="OP_NO_TICKET unavailable - OpenSSL version may be too old" ) def test_op_no_ticket(self): """ @@ -3372,9 +3276,7 @@ class TestConstants(object): @pytest.mark.skipif( OP_NO_COMPRESSION is None, - reason=( - "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" - ), + reason="OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" ) def test_op_no_compression(self): """ @@ -3448,26 +3350,23 @@ class TestMemoryBIO(object): """ 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 = Context(TLSv1_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, + verify_cb ) server_store = server_ctx.get_cert_store() server_ctx.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem) - ) + load_privatekey(FILETYPE_PEM, server_key_pem)) server_ctx.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem) - ) + 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 @@ -3482,19 +3381,17 @@ class TestMemoryBIO(object): """ # Now create the client side Connection. Similar boilerplate to the # above. - client_ctx = Context(SSLv23_METHOD) + client_ctx = Context(TLSv1_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, + verify_cb ) client_store = client_ctx.get_cert_store() client_ctx.use_privatekey( - load_privatekey(FILETYPE_PEM, client_key_pem) - ) + load_privatekey(FILETYPE_PEM, client_key_pem)) client_ctx.use_certificate( - load_certificate(FILETYPE_PEM, client_cert_pem) - ) + 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) @@ -3531,41 +3428,39 @@ class TestMemoryBIO(object): 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) + 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") + 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") + 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." + 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, - ) + 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], - ) + assert ( + interact_in_memory(client_conn, server_conn) == + (server_conn, important_message[::-1])) def test_socket_connect(self): """ @@ -3595,13 +3490,13 @@ class TestMemoryBIO(object): 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() + context = Context(TLSv1_METHOD) + client = socket() clientSSL = Connection(context, client) with pytest.raises(TypeError): clientSSL.bio_read(100) with pytest.raises(TypeError): - clientSSL.bio_write(b"foo") + clientSSL.bio_write("foo") with pytest.raises(TypeError): clientSSL.bio_shutdown() @@ -3685,7 +3580,7 @@ class TestMemoryBIO(object): `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) + ctx = Context(TLSv1_METHOD) with pytest.raises(TypeError): ctx.set_client_ca_list("spam") with pytest.raises(TypeError): @@ -3698,11 +3593,9 @@ class TestMemoryBIO(object): 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): @@ -3719,7 +3612,6 @@ class TestMemoryBIO(object): 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): @@ -3740,7 +3632,6 @@ class TestMemoryBIO(object): L = [sedesc, cldesc] ctx.set_client_ca_list(L) return L - self._check_client_ca_list(multiple_ca) def test_reset_ca_list(self): @@ -3761,7 +3652,6 @@ class TestMemoryBIO(object): 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): @@ -3781,7 +3671,6 @@ class TestMemoryBIO(object): 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): @@ -3789,7 +3678,7 @@ class TestMemoryBIO(object): `Context.add_client_ca` raises `TypeError` if called with a non-X509 object. """ - ctx = Context(SSLv23_METHOD) + ctx = Context(TLSv1_METHOD) with pytest.raises(TypeError): ctx.add_client_ca("spam") @@ -3804,7 +3693,6 @@ class TestMemoryBIO(object): 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): @@ -3822,7 +3710,6 @@ class TestMemoryBIO(object): 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): @@ -3843,7 +3730,6 @@ class TestMemoryBIO(object): 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): @@ -3864,7 +3750,6 @@ class TestMemoryBIO(object): ctx.set_client_ca_list([cadesc]) ctx.add_client_ca(secert) return [cadesc, sedesc] - self._check_client_ca_list(set_replaces_add_ca) @@ -3872,7 +3757,6 @@ class TestInfoConstants(object): """ Tests for assorted constants exposed for use in info callbacks. """ - def test_integers(self): """ All of the info constants are integers. @@ -3882,31 +3766,17 @@ class TestInfoConstants(object): 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, + 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, + SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE ]: assert const is None or isinstance(const, int) @@ -3916,7 +3786,6 @@ class TestRequires(object): 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 @@ -3954,7 +3823,6 @@ class TestOCSP(object): """ 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): @@ -3999,7 +3867,6 @@ class TestOCSP(object): 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") @@ -4025,7 +3892,7 @@ class TestOCSP(object): handshake_in_memory(client, server) assert len(called) == 1 - assert called[0] == b"" + assert called[0] == b'' def test_client_receives_servers_data(self): """ @@ -4108,7 +3975,7 @@ class TestOCSP(object): client_calls = [] def server_callback(*args): - return b"" + return b'' def client_callback(conn, ocsp_data, ignored): client_calls.append(ocsp_data) @@ -4119,13 +3986,12 @@ class TestOCSP(object): handshake_in_memory(client, server) assert len(client_calls) == 1 - assert client_calls[0] == b"" + 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 @@ -4142,7 +4008,6 @@ class TestOCSP(object): """ The callbacks thrown in the client callback bubble up to the caller. """ - class SentinelException(Exception): pass @@ -4162,7 +4027,6 @@ class TestOCSP(object): """ The callbacks thrown in the server callback bubble up to the caller. """ - class SentinelException(Exception): pass @@ -4182,9 +4046,8 @@ class TestOCSP(object): """ The server callback must return a bytestring, or a TypeError is thrown. """ - def server_callback(*args): - return self.sample_ocsp_data.decode("ascii") + return self.sample_ocsp_data.decode('ascii') def client_callback(*args): # pragma: nocover pytest.fail("Should not be called") diff --git a/tests/test_tsafe.py b/tests/test_tsafe.py new file mode 100644 index 0000000..8ffe35a --- /dev/null +++ b/tests/test_tsafe.py @@ -0,0 +1,23 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Unit tests for `OpenSSL.tsafe`. +""" + +from OpenSSL.SSL import TLSv1_METHOD, Context +from OpenSSL.tsafe import Connection + + +class TestConnection(object): + """ + Tests for `OpenSSL.tsafe.Connection`. + """ + def test_instantiation(self): + """ + `OpenSSL.tsafe.Connection` can be instantiated. + """ + # The following line should not throw an error. This isn't an ideal + # test. It would be great to refactor the other Connection tests so + # they could automatically be applied to this class too. + Connection(Context(TLSv1_METHOD), None) diff --git a/tests/test_util.py b/tests/test_util.py index 6224448..91847e0 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -7,7 +7,6 @@ class TestErrors(object): """ 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 diff --git a/tests/util.py b/tests/util.py index 75d2c8d..4464379 100644 --- a/tests/util.py +++ b/tests/util.py @@ -6,7 +6,7 @@ Helpers for the OpenSSL test suite, largely copied from U{Twisted<http://twistedmatrix.com/>}. """ -from six import PY2 +from six import PY3 # This is the UTF-8 encoding of the SNOWMAN unicode code point. @@ -59,7 +59,7 @@ class EqualityTestsMixin(object): An object compares equal to itself using the C{==} operator. """ o = self.anInstance() - assert o == o + assert (o == o) def test_identicalNe(self): """ @@ -75,7 +75,7 @@ class EqualityTestsMixin(object): """ a = self.anInstance() b = self.anInstance() - assert a == b + assert (a == b) def test_sameNe(self): """ @@ -102,7 +102,7 @@ class EqualityTestsMixin(object): """ a = self.anInstance() b = self.anotherInstance() - assert a != b + assert (a != b) def test_anotherTypeEq(self): """ @@ -120,14 +120,13 @@ class EqualityTestsMixin(object): """ a = self.anInstance() b = object() - assert a != b + 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(object): def __eq__(self, other): # Do something crazy and obvious. @@ -142,7 +141,6 @@ class EqualityTestsMixin(object): The result of comparison using C{!=} is delegated to the right-hand operand if it is of an unrelated type. """ - class Delegate(object): def __ne__(self, other): # Do something crazy and obvious. @@ -154,7 +152,7 @@ class EqualityTestsMixin(object): # The type name expected in warnings about using the wrong string type. -if PY2: - WARNING_TYPE_EXPECTED = "unicode" -else: +if PY3: WARNING_TYPE_EXPECTED = "str" +else: + WARNING_TYPE_EXPECTED = "unicode" @@ -1,5 +1,5 @@ [tox] -envlist = {pypy,pypy3,py27,py35,py36,py37,py38,py39}{,-cryptographyMaster,-cryptographyMinimum}{,-randomorder},py37-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report +envlist = {pypy,pypy3,py27,py34,py35,py36,py37}{,-cryptographyMaster,-cryptographyMinimum},py27-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report [testenv] whitelist_externals = @@ -10,8 +10,7 @@ extras = deps = coverage>=4.2 cryptographyMaster: git+https://github.com/pyca/cryptography.git - cryptographyMinimum: cryptography==3.2 - randomorder: pytest-randomly + cryptographyMinimum: cryptography==2.3.0 setenv = # Do not allow the executing environment to pollute the test environment # with extra packages. @@ -22,25 +21,40 @@ commands = coverage run --parallel -m OpenSSL.debug coverage run --parallel -m pytest -v {posargs} -[testenv:py37-twistedMaster] +[testenv:py27-twistedMaster] deps = - Twisted[all_non_platform] @ git+https://github.com/twisted/twisted -setenv = + # [tls,conch] syntax doesn't work here so we enumerate all dependencies. + git+https://github.com/twisted/twisted + idna + service_identity + bcrypt passenv = ARCHFLAGS CFLAGS LC_ALL LDFLAGS PATH LD_LIBRARY_PATH TERM commands = python -c "import OpenSSL.SSL; print(OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION))" python -c "import cryptography; print(cryptography.__version__)" python -m twisted.trial --reporter=text twisted +[testenv:py35-urllib3Master] +basepython=python3.5 +deps = + pyasn1 + ndg-httpsclient +passenv = ARCHFLAGS CFLAGS LC_ALL LDFLAGS PATH LD_LIBRARY_PATH TERM TRAVIS_INFRA +whitelist_externals = + rm +commands = + python -c "import OpenSSL.SSL; print(OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION))" + python -c "import cryptography; print(cryptography.__version__)" + {toxinidir}/.travis/install_urllib3.sh + pytest urllib3/test + rm -rf ./urllib3 + [testenv:flake8] -basepython = python3 deps = - black - flake8 + flake8 skip_install = true commands = - black --check . - flake8 . + flake8 src tests examples setup.py [testenv:pypi-readme] deps = @@ -69,6 +83,3 @@ skip_install = true commands = coverage combine coverage report - -[flake8] -ignore = E203,W503,W504 |