diff -Nru pyopenssl-19.1.0/CHANGELOG.rst pyopenssl-20.0.1/CHANGELOG.rst --- pyopenssl-19.1.0/CHANGELOG.rst 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/CHANGELOG.rst 2020-12-15 15:30:54.000000000 +0000 @@ -4,6 +4,60 @@ 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 `_ +- Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted + certificate file bundles and/or directories for verification. + `#943 `_ +- Added ``Context.set_keylog_callback`` to log key material. + `#910 `_ +- Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the + verified certificate chain of the peer. + `#894 `_. +- Make verification callback optional in ``Context.set_verify``. + If omitted, OpenSSL's default verification is used. + `#933 `_ +- Fixed a bug that could truncate or cause a zero-length key error due to a + null byte in private key passphrase in ``OpenSSL.crypto.load_privatekey`` + and ``OpenSSL.crypto.dump_privatekey``. + `#947 `_ + 19.1.0 (2019-11-18) ------------------- diff -Nru pyopenssl-19.1.0/debian/changelog pyopenssl-20.0.1/debian/changelog --- pyopenssl-19.1.0/debian/changelog 2020-04-23 04:48:52.000000000 +0000 +++ pyopenssl-20.0.1/debian/changelog 2021-01-10 06:35:33.000000000 +0000 @@ -1,3 +1,27 @@ +pyopenssl (20.0.1-1) unstable; urgency=medium + + [ Debian Janitor ] + * Update standards version to 4.5.0, no changes needed. + + [ Ondřej Nový ] + * d/control: Update Vcs-* fields with new Debian Python Team Salsa + layout. + + [ Sandro Tosi ] + * New upstream release. + * Use the new Debian Python Team contact name and address + * debian/copyright + - extend packaging copyright years + - update upstream copyright years + * debian/control + - bump deps on cryptography to >= 3.2 + - bump Standards-Version to 4.5.1 (no changes needed) + - bump dh compat to 13 + * debian/patches/0001-disable-test_set_default_verify_paths-since-it-tries.patch + - refresh patch + + -- Sandro Tosi Sun, 10 Jan 2021 01:35:33 -0500 + pyopenssl (19.1.0-2) unstable; urgency=medium * debian/python-openssl-doc.examples diff -Nru pyopenssl-19.1.0/debian/control pyopenssl-20.0.1/debian/control --- pyopenssl-19.1.0/debian/control 2020-04-23 04:48:52.000000000 +0000 +++ pyopenssl-20.0.1/debian/control 2021-01-10 06:35:33.000000000 +0000 @@ -2,12 +2,12 @@ Section: python Priority: optional Maintainer: Sandro Tosi -Uploaders: Debian Python Modules Team , -Build-Depends: debhelper-compat (= 12), +Uploaders: Debian Python Team , +Build-Depends: debhelper-compat (= 13), dh-python, python3-all, python3-cffi, - python3-cryptography (>= 2.8~), + python3-cryptography (>= 3.2~), python3-flaky, python3-pretend, python3-pytest (>= 3.0.1), @@ -15,10 +15,10 @@ python3-six, python3-sphinx (>= 1.0.7+dfsg), python3-sphinx-rtd-theme, -Standards-Version: 4.4.1 +Standards-Version: 4.5.1 Homepage: https://pyopenssl.org/ -Vcs-Git: https://salsa.debian.org/python-team/modules/pyopenssl.git -Vcs-Browser: https://salsa.debian.org/python-team/modules/pyopenssl +Vcs-Git: https://salsa.debian.org/python-team/packages/pyopenssl.git +Vcs-Browser: https://salsa.debian.org/python-team/packages/pyopenssl XS-Python-Version: all Package: python-openssl-doc @@ -43,7 +43,7 @@ Package: python3-openssl Architecture: all -Depends: python3-cryptography (>= 2.8~), +Depends: python3-cryptography (>= 3.2~), ${misc:Depends}, ${python3:Depends}, ${shlibs:Depends}, diff -Nru pyopenssl-19.1.0/debian/copyright pyopenssl-20.0.1/debian/copyright --- pyopenssl-19.1.0/debian/copyright 2020-04-23 04:48:52.000000000 +0000 +++ pyopenssl-20.0.1/debian/copyright 2021-01-10 06:35:33.000000000 +0000 @@ -3,11 +3,11 @@ Source: https://github.com/pyca/pyopenssl Files: * -Copyright: Copyright (C) 2008-2015 Jean-Paul Calderone, All rights reserved +Copyright: Copyright (C) 2008-2020 Jean-Paul Calderone, All rights reserved License: Apache-2.0 Files: debian/* -Copyright: Copyright (C) 2008-2019, Sandro Tosi +Copyright: Copyright (C) 2008-2021, Sandro Tosi License: Apache-2.0 License: Apache-2.0 diff -Nru pyopenssl-19.1.0/debian/patches/0001-disable-test_set_default_verify_paths-since-it-tries.patch pyopenssl-20.0.1/debian/patches/0001-disable-test_set_default_verify_paths-since-it-tries.patch --- pyopenssl-19.1.0/debian/patches/0001-disable-test_set_default_verify_paths-since-it-tries.patch 2020-04-23 04:48:52.000000000 +0000 +++ pyopenssl-20.0.1/debian/patches/0001-disable-test_set_default_verify_paths-since-it-tries.patch 2021-01-10 06:35:33.000000000 +0000 @@ -7,11 +7,9 @@ tests/test_ssl.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) -diff --git a/tests/test_ssl.py b/tests/test_ssl.py -index e2681e3..6ca2f2b 100644 --- a/tests/test_ssl.py +++ b/tests/test_ssl.py -@@ -1156,20 +1156,22 @@ class TestContext(object): +@@ -1252,21 +1252,22 @@ class TestContext(object): # internet which has such a certificate. Connecting to the network # in a unit test is bad, but it's the only way I can think of to # really test this. -exarkun @@ -19,8 +17,16 @@ - 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, +- ) ++ pass ++ #context = Context(SSLv23_METHOD) ++ #context.set_default_verify_paths() ++ #context.set_verify( ++ # VERIFY_PEER, ++ # lambda conn, cert, errno, depth, preverify_ok: preverify_ok, ++ #) + - client = socket_any_family() - client.connect(("encrypted.google.com", 443)) - clientSSL = Connection(context, client) @@ -29,14 +35,6 @@ - clientSSL.do_handshake() - clientSSL.send(b"GET / HTTP/1.0\r\n\r\n") - assert clientSSL.recv(1024) -+ pass -+ -+ #context = Context(SSLv23_METHOD) -+ #context.set_default_verify_paths() -+ #context.set_verify( -+ # VERIFY_PEER, -+ # lambda conn, cert, errno, depth, preverify_ok: preverify_ok) -+ + #client = socket_any_family() + #client.connect(("encrypted.google.com", 443)) + #clientSSL = Connection(context, client) diff -Nru pyopenssl-19.1.0/doc/conf.py pyopenssl-20.0.1/doc/conf.py --- pyopenssl-19.1.0/doc/conf.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/doc/conf.py 2020-12-15 15:30:54.000000000 +0000 @@ -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 containing dir. +# This file is execfile()d with the current directory set to its parent dir. # # Note that not all possible configuration values are present in this # autogenerated file. @@ -11,7 +11,6 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import datetime import codecs import os import re @@ -32,8 +31,9 @@ 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 @@ # 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,73 +87,74 @@ # 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 # " v 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, @@ -162,96 +163,92 @@ # 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 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 -Nru pyopenssl-19.1.0/doc/index.rst pyopenssl-20.0.1/doc/index.rst --- pyopenssl-19.1.0/doc/index.rst 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/doc/index.rst 2020-12-15 15:30:54.000000000 +0000 @@ -20,7 +20,6 @@ api internals -There are also `examples in the pyOpenSSL repository `_ that may help you getting started. Meta diff -Nru pyopenssl-19.1.0/INSTALL.rst pyopenssl-20.0.1/INSTALL.rst --- pyopenssl-19.1.0/INSTALL.rst 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/INSTALL.rst 2020-12-15 15:30:54.000000000 +0000 @@ -25,7 +25,6 @@ pyOpenSSL supports the same platforms and releases as the upstream cryptography project `does `_. Currently that means: -- 1.0.1 - 1.0.2 - 1.1.0 - 1.1.1 diff -Nru pyopenssl-19.1.0/MANIFEST.in pyopenssl-20.0.1/MANIFEST.in --- pyopenssl-19.1.0/MANIFEST.in 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/MANIFEST.in 2020-12-15 15:30:54.000000000 +0000 @@ -1,8 +1,6 @@ include LICENSE MANIFEST.in *.rst tox.ini .coveragerc -exclude leakcheck codecov.yml +exclude codecov.yml recursive-include tests *.py recursive-include doc * -recursive-include rpm * -recursive-exclude leakcheck *.py *.pem prune doc/_build prune .travis diff -Nru pyopenssl-19.1.0/PKG-INFO pyopenssl-20.0.1/PKG-INFO --- pyopenssl-19.1.0/PKG-INFO 2019-11-18 04:59:19.000000000 +0000 +++ pyopenssl-20.0.1/PKG-INFO 2020-12-15 15:31:35.327834800 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pyOpenSSL -Version: 19.1.0 +Version: 20.0.1 Summary: Python wrapper module around the OpenSSL library Home-page: https://pyopenssl.org/ Author: The pyOpenSSL developers @@ -14,8 +14,8 @@ :target: https://pyopenssl.org/en/stable/ :alt: Stable Docs - .. image:: https://travis-ci.org/pyca/pyopenssl.svg?branch=master - :target: https://travis-ci.org/pyca/pyopenssl + .. image:: https://travis-ci.com/pyca/pyopenssl.svg?branch=master + :target: https://travis-ci.com/pyca/pyopenssl :alt: Build status .. image:: https://codecov.io/github/pyca/pyopenssl/branch/master/graph/badge.svg @@ -58,6 +58,60 @@ Release Information =================== + 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 `_ + - Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted + certificate file bundles and/or directories for verification. + `#943 `_ + - Added ``Context.set_keylog_callback`` to log key material. + `#910 `_ + - Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the + verified certificate chain of the peer. + `#894 `_. + - Make verification callback optional in ``Context.set_verify``. + If omitted, OpenSSL's default verification is used. + `#933 `_ + - Fixed a bug that could truncate or cause a zero-length key error due to a + null byte in private key passphrase in ``OpenSSL.crypto.load_privatekey`` + and ``OpenSSL.crypto.dump_privatekey``. + `#947 `_ + 19.1.0 (2019-11-18) ------------------- @@ -100,15 +154,16 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking -Provides-Extra: test +Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* Provides-Extra: docs +Provides-Extra: test diff -Nru pyopenssl-19.1.0/README.rst pyopenssl-20.0.1/README.rst --- pyopenssl-19.1.0/README.rst 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/README.rst 2020-12-15 15:30:54.000000000 +0000 @@ -6,8 +6,8 @@ :target: https://pyopenssl.org/en/stable/ :alt: Stable Docs -.. image:: https://travis-ci.org/pyca/pyopenssl.svg?branch=master - :target: https://travis-ci.org/pyca/pyopenssl +.. image:: https://travis-ci.com/pyca/pyopenssl.svg?branch=master + :target: https://travis-ci.com/pyca/pyopenssl :alt: Build status .. image:: https://codecov.io/github/pyca/pyopenssl/branch/master/graph/badge.svg diff -Nru pyopenssl-19.1.0/rpm/build_script pyopenssl-20.0.1/rpm/build_script --- pyopenssl-19.1.0/rpm/build_script 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/rpm/build_script 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -make -C doc text html diff -Nru pyopenssl-19.1.0/setup.py pyopenssl-20.0.1/setup.py --- pyopenssl-19.1.0/setup.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/setup.py 2020-12-15 15:30:54.000000000 +0000 @@ -36,8 +36,7 @@ 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) @@ -46,13 +45,17 @@ 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,45 +70,36 @@ 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.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - - '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.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", ], - + 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>=2.8", - "six>=1.5.2" + "cryptography>=3.2", + "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 -Nru pyopenssl-19.1.0/src/OpenSSL/crypto.py pyopenssl-20.0.1/src/OpenSSL/crypto.py --- pyopenssl-19.1.0/src/OpenSSL/crypto.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/crypto.py 2020-12-15 15:30:54.000000000 +0000 @@ -1,3 +1,4 @@ +import calendar import datetime from base64 import b16encode @@ -7,9 +8,10 @@ from six import ( integer_types as _integer_types, text_type as _text_type, - PY3 as _PY3) + PY2 as _PY2, +) -from cryptography import x509 +from cryptography import utils, x509 from cryptography.hazmat.primitives.asymmetric import dsa, rsa from OpenSSL._util import ( @@ -18,48 +20,49 @@ 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 @@ -93,6 +96,7 @@ triggering this side effect unless _get_backend is called. """ from cryptography.hazmat.backends.openssl.backend import backend + return backend @@ -135,7 +139,7 @@ """ 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)[:] @@ -172,7 +176,7 @@ @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 ( @@ -195,7 +199,8 @@ _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]) @@ -219,6 +224,7 @@ """ A class representing an DSA or RSA public key or key pair. """ + _only_public = False _initialized = True @@ -257,8 +263,15 @@ .. 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 @@ -345,7 +358,7 @@ rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) rsa = _ffi.gc(rsa, _lib.RSA_free) result = _lib.RSA_check_key(rsa) - if result: + if result == 1: return True _raise_current_error() @@ -375,10 +388,11 @@ instances each of which represents one curve supported by the system. @type _curves: :py:type:`NoneType` or :py:type:`set` """ + _curves = None - if _PY3: - # This only necessary on Python 3. Morever, it is broken on Python 2. + if not _PY2: + # This only necessary on Python 3. Moreover, it is broken on Python 2. def __ne__(self, other): """ Implement cooperation with the right-hand side argument of ``!=``. @@ -401,14 +415,12 @@ 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): @@ -541,14 +553,16 @@ 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: @@ -569,10 +583,11 @@ 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() @@ -608,9 +623,9 @@ _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]) @@ -622,6 +637,7 @@ return NotImplemented result = _lib.X509_NAME_cmp(self._name, other._name) return op(result, 0) + return f __eq__ = _cmp(__eq__) @@ -639,11 +655,13 @@ """ 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 "" % ( - _native(_ffi.string(result_buffer)),) + _native(_ffi.string(result_buffer)), + ) def hash(self): """ @@ -664,7 +682,7 @@ :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) @@ -691,8 +709,9 @@ # 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 @@ -793,7 +812,8 @@ 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) @@ -843,7 +863,7 @@ .. 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)[:] @@ -869,8 +889,9 @@ .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateSigningRequest + _CertificateSigningRequest, ) + backend = _get_backend() return _CertificateSigningRequest(backend, self._req) @@ -996,9 +1017,20 @@ """ 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) - ext._extension = _lib.sk_X509_EXTENSION_value(native_exts_obj, i) + extension = _lib.X509_EXTENSION_dup( + _lib.sk_X509_EXTENSION_value(native_exts_obj, i) + ) + ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) exts.append(ext) return exts @@ -1052,6 +1084,7 @@ """ An X.509 certificate. """ + def __init__(self): x509 = _lib.X509_new() _openssl_assert(x509 != _ffi.NULL) @@ -1077,6 +1110,7 @@ .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import _Certificate + backend = _get_backend() return _Certificate(backend, self._x509) @@ -1218,12 +1252,16 @@ 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): """ @@ -1248,7 +1286,7 @@ 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**") @@ -1259,7 +1297,8 @@ 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() @@ -1304,7 +1343,7 @@ if not isinstance(amount, int): raise TypeError("amount must be an integer") - notAfter = _lib.X509_get_notAfter(self._x509) + notAfter = _lib.X509_getm_notAfter(self._x509) _lib.X509_gmtime_adj(notAfter, amount) def gmtime_adj_notBefore(self, amount): @@ -1317,7 +1356,7 @@ if not isinstance(amount, int): raise TypeError("amount must be an integer") - notBefore = _lib.X509_get_notBefore(self._x509) + notBefore = _lib.X509_getm_notBefore(self._x509) _lib.X509_gmtime_adj(notBefore, amount) def has_expired(self): @@ -1346,7 +1385,7 @@ :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ - return self._get_boundary_time(_lib.X509_get_notBefore) + return self._get_boundary_time(_lib.X509_getm_notBefore) def _set_boundary_time(self, which, when): return _set_asn1_time(which(self._x509), when) @@ -1362,7 +1401,7 @@ :param bytes when: A timestamp string. :return: ``None`` """ - return self._set_boundary_time(_lib.X509_get_notBefore, when) + return self._set_boundary_time(_lib.X509_getm_notBefore, when) def get_notAfter(self): """ @@ -1375,7 +1414,7 @@ :return: A timestamp string, or ``None`` if there is none. :rtype: bytes or NoneType """ - return self._get_boundary_time(_lib.X509_get_notAfter) + return self._get_boundary_time(_lib.X509_getm_notAfter) def set_notAfter(self, when): """ @@ -1388,7 +1427,7 @@ :param bytes when: A timestamp string. :return: ``None`` """ - return self._set_boundary_time(_lib.X509_get_notAfter, when) + return self._set_boundary_time(_lib.X509_getm_notAfter, when) def _get_name(self, which): name = X509Name.__new__(X509Name) @@ -1524,6 +1563,7 @@ .. _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 @@ -1574,16 +1614,8 @@ if not isinstance(cert, X509): raise TypeError() - # 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() + res = _lib.X509_STORE_add_cert(self._store, cert._x509) + _openssl_assert(res == 1) def add_crl(self, crl): """ @@ -1644,9 +1676,58 @@ param = _lib.X509_VERIFY_PARAM_new() param = _ffi.gc(param, _lib.X509_VERIFY_PARAM_free) - _lib.X509_VERIFY_PARAM_set_time(param, int(vfy_time.strftime('%s'))) + _lib.X509_VERIFY_PARAM_set_time( + param, calendar.timegm(vfy_time.timetuple()) + ) _openssl_assert(_lib.X509_STORE_set1_param(self._store, param) != 0) + def load_locations(self, cafile, 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() + class X509StoreContextError(Exception): """ @@ -1675,21 +1756,54 @@ 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): + def __init__(self, store, certificate, chain=None): 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. @@ -1698,7 +1812,7 @@ :meth:`_cleanup` will leak memory. """ ret = _lib.X509_STORE_CTX_init( - self._store_ctx, self._store._store, self._cert._x509, _ffi.NULL + self._store_ctx, self._store._store, self._cert._x509, self._chain ) if ret <= 0: _raise_current_error() @@ -1722,8 +1836,13 @@ 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`. @@ -1765,6 +1884,45 @@ 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): """ @@ -1787,8 +1945,7 @@ 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() @@ -1817,9 +1974,10 @@ else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") + "FILETYPE_TEXT" + ) - assert result_code == 1 + _openssl_assert(result_code == 1) return _bio_to_string(bio) @@ -1873,7 +2031,8 @@ 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") @@ -1883,8 +2042,14 @@ 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) @@ -1892,15 +2057,13 @@ 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) @@ -1911,6 +2074,7 @@ """ 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 @@ -1950,7 +2114,8 @@ 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): @@ -2001,7 +2166,7 @@ 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() @@ -2013,7 +2178,8 @@ 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): @@ -2095,8 +2261,9 @@ .. versionadded:: 17.1.0 """ from cryptography.hazmat.backends.openssl.x509 import ( - _CertificateRevocationList + _CertificateRevocationList, ) + backend = _get_backend() return _CertificateRevocationList(backend, self._crl) @@ -2203,7 +2370,7 @@ def set_nextUpdate(self, when): """ - Set when the CRL will next be udpated. + Set when the CRL will next be updated. The timestamp is formatted as an ASN.1 TIME:: @@ -2236,13 +2403,15 @@ 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. @@ -2500,10 +2669,17 @@ 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) @@ -2618,9 +2794,7 @@ def callback(self): if self._passphrase is None: return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return _ffi.NULL - elif callable(self._passphrase): + elif isinstance(self._passphrase, bytes) or callable(self._passphrase): return _ffi.callback("pem_password_cb", self._read_passphrase) else: raise TypeError( @@ -2631,9 +2805,7 @@ def callback_args(self): if self._passphrase is None: return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return self._passphrase - elif callable(self._passphrase): + elif isinstance(self._passphrase, bytes) or callable(self._passphrase): return _ffi.NULL else: raise TypeError( @@ -2653,12 +2825,15 @@ def _read_passphrase(self, buf, size, rwflag, userdata): try: - if self._more_args: - result = self._passphrase(size, rwflag, userdata) + if callable(self._passphrase): + if self._more_args: + result = self._passphrase(size, rwflag, userdata) + else: + result = self._passphrase(rwflag) else: - result = self._passphrase(rwflag) + result = self._passphrase if not isinstance(result, bytes): - raise ValueError("String expected") + raise ValueError("Bytes expected") if len(result) > size: if self._truncate: result = result[:size] @@ -2667,7 +2842,7 @@ "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) @@ -2692,7 +2867,8 @@ 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: @@ -2728,7 +2904,8 @@ 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) @@ -2827,7 +3004,8 @@ 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])[:] @@ -2891,9 +3069,10 @@ else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") + "FILETYPE_TEXT" + ) - assert ret == 1 + _openssl_assert(ret == 1) return _bio_to_string(bio) @@ -2956,6 +3135,17 @@ 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 @@ -3043,6 +3233,17 @@ 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. @@ -3061,4 +3262,4 @@ # 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 -Nru pyopenssl-19.1.0/src/OpenSSL/debug.py pyopenssl-20.0.1/src/OpenSSL/debug.py --- pyopenssl-19.1.0/src/OpenSSL/debug.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/debug.py 2020-12-15 15:30:54.000000000 +0000 @@ -16,7 +16,7 @@ cffi: {cffi} cryptography's compiled against OpenSSL: {crypto_openssl_compile} cryptography's linked OpenSSL: {crypto_openssl_link} -Pythons's OpenSSL: {python_openssl} +Python's OpenSSL: {python_openssl} Python executable: {python} Python version: {python_version} Platform: {platform} diff -Nru pyopenssl-19.1.0/src/OpenSSL/__init__.py pyopenssl-20.0.1/src/OpenSSL/__init__.py --- pyopenssl-19.1.0/src/OpenSSL/__init__.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/__init__.py 2020-12-15 15:30:54.000000000 +0000 @@ -7,14 +7,26 @@ 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 -Nru pyopenssl-19.1.0/src/OpenSSL/SSL.py pyopenssl-20.0.1/src/OpenSSL/SSL.py --- pyopenssl-19.1.0/src/OpenSSL/SSL.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/SSL.py 2020-12-15 15:30:54.000000000 +0000 @@ -1,15 +1,12 @@ import os import socket -import warnings from sys import platform from functools import wraps, partial from itertools import count, chain from weakref import WeakValueDictionary from errno import errorcode -from six import ( - binary_type as _binary_type, integer_types as integer_types, int2byte, - indexbytes) +from six import integer_types, int2byte, indexbytes from OpenSSL._util import ( UNSPECIFIED as _UNSPECIFIED, @@ -25,100 +22,108 @@ ) 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", + "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", ] 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 @@ -196,17 +201,6 @@ 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 @@ -335,96 +329,7 @@ return 0 self.callback = _ffi.callback( - "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 - ) - - -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 + "int (*)(int, X509_STORE_CTX *)", wrapper ) @@ -451,17 +356,17 @@ 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'' + outbytes = b"" any_accepted = False - elif not isinstance(outbytes, _binary_type): + elif not isinstance(outbytes, bytes): raise TypeError( "ALPN callback must return a bytestring or the " "special NO_OVERLAPPING_PROTOCOLS sentinel value." @@ -484,9 +389,11 @@ return _lib.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, ) @@ -529,7 +436,7 @@ # Call the callback. ocsp_data = callback(conn, data) - if not isinstance(ocsp_data, _binary_type): + if not isinstance(ocsp_data, bytes): raise TypeError("OCSP callback must return a bytestring.") # If the OCSP data was provided, we will pass it to OpenSSL. @@ -598,7 +505,7 @@ 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)[:] @@ -630,7 +537,8 @@ 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 @@ -644,11 +552,6 @@ return _ffi.string(_lib.SSLeay_version(type)) -def _warn_npn(): - warnings.warn("NPN is deprecated. Protocols should switch to using ALPN.", - DeprecationWarning, stacklevel=3) - - def _make_requires(flag, error): """ Builds a decorator that ensures that functions that rely on OpenSSL @@ -659,11 +562,14 @@ ``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 @@ -671,13 +577,13 @@ 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_alpn = _make_requires( - _lib.Cryptography_HAS_ALPN, "ALPN not available" +_requires_keylog = _make_requires( + getattr(_lib, "Cryptography_HAS_KEYLOG", None), "Key logging not available" ) @@ -689,6 +595,7 @@ .. versionadded:: 0.14 """ + pass @@ -700,6 +607,7 @@ :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or TLSv1_METHOD. """ + _methods = { SSLv2_METHOD: "SSLv2_method", SSLv3_METHOD: "SSLv3_method", @@ -711,7 +619,8 @@ _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): @@ -729,14 +638,11 @@ _openssl_assert(context != _ffi.NULL) context = _ffi.gc(context, _lib.SSL_CTX_free) - # 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 + # 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) self._context = context self._passphrase_helper = None @@ -745,12 +651,9 @@ 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 @@ -795,8 +698,10 @@ @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): """ @@ -823,7 +728,8 @@ 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): @@ -853,9 +759,9 @@ # 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") @@ -866,13 +772,12 @@ # 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): @@ -882,8 +787,8 @@ :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): @@ -1001,7 +906,8 @@ 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() @@ -1057,11 +963,8 @@ """ 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): @@ -1091,21 +994,22 @@ """ return _lib.SSL_CTX_get_session_cache_mode(self._context) - def set_verify(self, mode, callback): + def set_verify(self, mode, callback=None): """ - et the verification flags for this Context object to *mode* and specify - that *callback* should be used for verification callbacks. + Set the verification flags for this Context object to *mode* and + specify that *callback* should be used for verification callbacks. :param mode: The verify mode, this should be one of :const:`VERIFY_NONE` and :const:`VERIFY_PEER`. If :const:`VERIFY_PEER` is used, *mode* can be OR:ed with :const:`VERIFY_FAIL_IF_NO_PEER_CERT` and :const:`VERIFY_CLIENT_ONCE` to further control the behaviour. - :param callback: The 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. + :param callback: The optional Python verification callback to use. + This should take five arguments: A Connection object, an X509 + object, and three integer variables, which are in turn potential + error number, error depth and return code. *callback* should + return True if verification passes and False otherwise. + If omitted, OpenSSL's default verification is used. :return: None See SSL_CTX_set_verify(3SSL) for further details. @@ -1113,12 +1017,17 @@ if not isinstance(mode, integer_types): raise TypeError("mode must be an integer") - if not callable(callback): - raise TypeError("callback must be callable") + if callback is None: + self._verify_helper = None + self._verify_callback = None + _lib.SSL_CTX_set_verify(self._context, mode, _ffi.NULL) + else: + if not callable(callback): + raise TypeError("callback must be callable") - self._verify_helper = _VerifyHelper(callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) + 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): """ @@ -1169,7 +1078,8 @@ dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) dh = _ffi.gc(dh, _lib.DH_free) - _lib.SSL_CTX_set_tmp_dh(self._context, dh) + res = _lib.SSL_CTX_set_tmp_dh(self._context, dh) + _openssl_assert(res == 1) def set_tmp_ecdh(self, curve): """ @@ -1207,19 +1117,17 @@ # 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' - ] - ): + 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', + "SSL routines", + "SSL_CTX_set_cipher_list", + "no cipher match", ), ], ) @@ -1245,9 +1153,7 @@ 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) @@ -1278,7 +1184,8 @@ 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): @@ -1316,13 +1223,41 @@ 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()`) @@ -1393,15 +1328,18 @@ .. 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): """ @@ -1418,45 +1356,6 @@ _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 - `_ 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 - """ - _warn_npn() - 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 - """ - _warn_npn() - 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): """ @@ -1470,7 +1369,7 @@ """ # 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) ) @@ -1497,7 +1396,8 @@ 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): """ @@ -1559,8 +1459,6 @@ class Connection(object): - """ - """ _reverse_mapping = WeakValueDictionary() def __init__(self, context, socket=None): @@ -1584,19 +1482,18 @@ 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: @@ -1614,7 +1511,8 @@ 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): @@ -1623,19 +1521,16 @@ 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: @@ -1782,9 +1677,7 @@ # 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._ssl, data + total_sent, min(left_to_send, 2147483647) ) self._raise_ssl_error(self._ssl, result) total_sent += result @@ -1808,6 +1701,7 @@ 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): @@ -2074,7 +1968,8 @@ :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): """ @@ -2133,7 +2028,7 @@ if session == _ffi.NULL: return None length = _lib.SSL_get_server_random(self._ssl, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_server_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2149,7 +2044,7 @@ return None length = _lib.SSL_get_client_random(self._ssl, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_get_client_random(self._ssl, outp, length) return _ffi.buffer(outp, length)[:] @@ -2165,7 +2060,7 @@ return None length = _lib.SSL_SESSION_get_master_key(session, _ffi.NULL, 0) - assert length > 0 + _openssl_assert(length > 0) outp = _no_zero_allocator("unsigned char[]", length) _lib.SSL_SESSION_get_master_key(session, outp, length) return _ffi.buffer(outp, length)[:] @@ -2187,10 +2082,16 @@ 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)[:] @@ -2226,6 +2127,22 @@ 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) @@ -2237,13 +2154,26 @@ if cert_stack == _ffi.NULL: return None - 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 + 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) def want_read(self): """ @@ -2311,8 +2241,7 @@ raise TypeError("session must be a Session instance") result = _lib.SSL_set_session(self._ssl, session._session) - if not result: - _raise_current_error() + _openssl_assert(result == 1) def _get_finished_message(self, function): """ @@ -2445,24 +2374,6 @@ 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 - """ - _warn_npn() - 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): """ @@ -2476,7 +2387,7 @@ """ # 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) ) @@ -2499,7 +2410,7 @@ _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])[:] diff -Nru pyopenssl-19.1.0/src/OpenSSL/tsafe.py pyopenssl-20.0.1/src/OpenSSL/tsafe.py --- pyopenssl-19.1.0/src/OpenSSL/tsafe.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/tsafe.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -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 -Nru pyopenssl-19.1.0/src/OpenSSL/_util.py pyopenssl-20.0.1/src/OpenSSL/_util.py --- pyopenssl-19.1.0/src/OpenSSL/_util.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/_util.py 2020-12-15 15:30:54.000000000 +0000 @@ -1,7 +1,7 @@ import sys import warnings -from six import PY2, binary_type, text_type +from six import PY2, text_type from cryptography.hazmat.bindings.openssl.binding import Binding @@ -46,10 +46,13 @@ 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) @@ -59,6 +62,7 @@ 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. @@ -79,13 +83,13 @@ :raise TypeError: The input is neither :py:class:`bytes` nor :py:class:`unicode`. """ - if not isinstance(s, (binary_type, text_type)): + if not isinstance(s, (bytes, text_type)): raise TypeError("%r is neither bytes nor unicode" % s) if PY2: if isinstance(s, text_type): return s.encode("utf-8") else: - if isinstance(s, binary_type): + if isinstance(s, bytes): return s.decode("utf-8") return s @@ -99,7 +103,7 @@ :return: An instance of :py:class:`bytes`. """ - if isinstance(s, binary_type): + if isinstance(s, bytes): return s elif isinstance(s, text_type): return s.encode(sys.getfilesystemencoding()) @@ -108,9 +112,13 @@ if PY2: + def byte_string(s): return s + + else: + def byte_string(s): return s.encode("charmap") @@ -141,21 +149,10 @@ warnings.warn( _TEXT_WARNING.format(label), category=DeprecationWarning, - stacklevel=3 + stacklevel=3, ) - return obj.encode('utf-8') + return obj.encode("utf-8") return obj -try: - # newer versions of cffi free the buffer deterministically - with ffi.from_buffer(b""): - pass - from_buffer = ffi.from_buffer -except AttributeError: - # cffi < 0.12 frees the buffer with refcounting gc - from contextlib import contextmanager - - @contextmanager - def from_buffer(*args): - yield ffi.from_buffer(*args) +from_buffer = ffi.from_buffer diff -Nru pyopenssl-19.1.0/src/OpenSSL/version.py pyopenssl-20.0.1/src/OpenSSL/version.py --- pyopenssl-19.1.0/src/OpenSSL/version.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/src/OpenSSL/version.py 2020-12-15 15:30:54.000000000 +0000 @@ -7,11 +7,17 @@ """ __all__ = [ - "__author__", "__copyright__", "__email__", "__license__", "__summary__", - "__title__", "__uri__", "__version__", + "__author__", + "__copyright__", + "__email__", + "__license__", + "__summary__", + "__title__", + "__uri__", + "__version__", ] -__version__ = "19.1.0" +__version__ = "20.0.1" __title__ = "pyOpenSSL" __uri__ = "https://pyopenssl.org/" @@ -19,4 +25,4 @@ __author__ = "The pyOpenSSL developers" __email__ = "cryptography-dev@python.org" __license__ = "Apache License, Version 2.0" -__copyright__ = "Copyright 2001-2017 {0}".format(__author__) +__copyright__ = "Copyright 2001-2020 {0}".format(__author__) diff -Nru pyopenssl-19.1.0/src/pyOpenSSL.egg-info/PKG-INFO pyopenssl-20.0.1/src/pyOpenSSL.egg-info/PKG-INFO --- pyopenssl-19.1.0/src/pyOpenSSL.egg-info/PKG-INFO 2019-11-18 04:59:19.000000000 +0000 +++ pyopenssl-20.0.1/src/pyOpenSSL.egg-info/PKG-INFO 2020-12-15 15:31:35.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pyOpenSSL -Version: 19.1.0 +Version: 20.0.1 Summary: Python wrapper module around the OpenSSL library Home-page: https://pyopenssl.org/ Author: The pyOpenSSL developers @@ -14,8 +14,8 @@ :target: https://pyopenssl.org/en/stable/ :alt: Stable Docs - .. image:: https://travis-ci.org/pyca/pyopenssl.svg?branch=master - :target: https://travis-ci.org/pyca/pyopenssl + .. image:: https://travis-ci.com/pyca/pyopenssl.svg?branch=master + :target: https://travis-ci.com/pyca/pyopenssl :alt: Build status .. image:: https://codecov.io/github/pyca/pyopenssl/branch/master/graph/badge.svg @@ -58,6 +58,60 @@ Release Information =================== + 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 `_ + - Added ``OpenSSL.crypto.X509Store.load_locations`` to set trusted + certificate file bundles and/or directories for verification. + `#943 `_ + - Added ``Context.set_keylog_callback`` to log key material. + `#910 `_ + - Added ``OpenSSL.SSL.Connection.get_verified_chain`` to retrieve the + verified certificate chain of the peer. + `#894 `_. + - Make verification callback optional in ``Context.set_verify``. + If omitted, OpenSSL's default verification is used. + `#933 `_ + - Fixed a bug that could truncate or cause a zero-length key error due to a + null byte in private key passphrase in ``OpenSSL.crypto.load_privatekey`` + and ``OpenSSL.crypto.dump_privatekey``. + `#947 `_ + 19.1.0 (2019-11-18) ------------------- @@ -100,15 +154,16 @@ Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking -Provides-Extra: test +Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.* Provides-Extra: docs +Provides-Extra: test diff -Nru pyopenssl-19.1.0/src/pyOpenSSL.egg-info/requires.txt pyopenssl-20.0.1/src/pyOpenSSL.egg-info/requires.txt --- pyopenssl-19.1.0/src/pyOpenSSL.egg-info/requires.txt 2019-11-18 04:59:19.000000000 +0000 +++ pyopenssl-20.0.1/src/pyOpenSSL.egg-info/requires.txt 2020-12-15 15:31:35.000000000 +0000 @@ -1,4 +1,4 @@ -cryptography>=2.8 +cryptography>=3.2 six>=1.5.2 [docs] diff -Nru pyopenssl-19.1.0/src/pyOpenSSL.egg-info/SOURCES.txt pyopenssl-20.0.1/src/pyOpenSSL.egg-info/SOURCES.txt --- pyopenssl-19.1.0/src/pyOpenSSL.egg-info/SOURCES.txt 2019-11-18 04:59:19.000000000 +0000 +++ pyopenssl-20.0.1/src/pyOpenSSL.egg-info/SOURCES.txt 2020-12-15 15:31:35.000000000 +0000 @@ -28,14 +28,12 @@ doc/images/pyopenssl-icon.png doc/images/pyopenssl-logo.png doc/images/pyopenssl.svg -rpm/build_script src/OpenSSL/SSL.py src/OpenSSL/__init__.py src/OpenSSL/_util.py src/OpenSSL/crypto.py src/OpenSSL/debug.py src/OpenSSL/rand.py -src/OpenSSL/tsafe.py src/OpenSSL/version.py src/pyOpenSSL.egg-info/PKG-INFO src/pyOpenSSL.egg-info/SOURCES.txt @@ -49,6 +47,5 @@ tests/test_debug.py tests/test_rand.py tests/test_ssl.py -tests/test_tsafe.py tests/test_util.py tests/util.py \ No newline at end of file diff -Nru pyopenssl-19.1.0/tests/conftest.py pyopenssl-20.0.1/tests/conftest.py --- pyopenssl-19.1.0/tests/conftest.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/conftest.py 2020-12-15 15:30:54.000000000 +0000 @@ -12,7 +12,7 @@ return "OpenSSL: {openssl}\ncryptography: {cryptography}".format( openssl=OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION), - cryptography=cryptography.__version__ + cryptography=cryptography.__version__, ) diff -Nru pyopenssl-19.1.0/tests/memdbg.py pyopenssl-20.0.1/tests/memdbg.py --- pyopenssl-19.1.0/tests/memdbg.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/memdbg.py 2020-12-15 15:30:54.000000000 +0000 @@ -5,8 +5,8 @@ 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,18 +16,22 @@ 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 #include #include - """, libraries=["crypto"]) + """, + libraries=["crypto"], +) C = _ffi.dlopen(None) verbose = False @@ -80,8 +84,8 @@ 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 -Nru pyopenssl-19.1.0/tests/test_crypto.py pyopenssl-20.0.1/tests/test_crypto.py --- pyopenssl-19.1.0/tests/test_crypto.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/test_crypto.py 2020-12-15 15:30:54.000000000 +0000 @@ -10,11 +10,10 @@ 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 @@ -28,7 +27,7 @@ X509Store, X509StoreFlags, X509StoreContext, - X509StoreContextError + X509StoreContextError, ) from OpenSSL.crypto import X509Req from OpenSSL.crypto import X509Extension @@ -42,9 +41,20 @@ from OpenSSL.crypto import CRL, Revoked, dump_crl, load_crl from OpenSSL.crypto import NetscapeSPKI from OpenSSL.crypto import ( - sign, verify, get_elliptic_curve, get_elliptic_curves) + sign, + verify, + get_elliptic_curve, + get_elliptic_curves, +) -from .util import EqualityTestsMixin, is_consistent_type, WARNING_TYPE_EXPECTED +from OpenSSL._util import ffi as _ffi, lib as _lib + +from .util import ( + EqualityTestsMixin, + is_consistent_type, + WARNING_TYPE_EXPECTED, + NON_ASCII, +) def normalize_privatekey_pem(pem): @@ -78,213 +88,398 @@ """ root_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIIC6TCCAlKgAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE +MIIE7jCCA1agAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU -ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTcwNjExMjIzMjU5 -WhcNMzcwNjA2MjIzMjU5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO +ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAwODAyMTcxMTE5 +WhcNNDcxMjIwMTcxMTE5WjBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMCSUwxEDAO BgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMTD1Rlc3Rp -bmcgUm9vdCBDQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA+ZpC6Yu6ukTn -bu5IQd0vWmpwNGZbO773xjpgfNP8nspYRqbIwI1np9FbUkJHvzZRDxrTt/LbFewr -LhZ0prHIbwJxq3CZe+m9FDjh1IA0yKEcQukA1N3JWnoMLKwQPrCRAW6seUXV2yER -onDxv/KkOGZtUijoKLXG8ImqK9ssWdsCAwEAAaOBuzCBuDAdBgNVHQ4EFgQUg1V3 -LV4h8UkMCSTnVAkSjch+BK4wgYgGA1UdIwSBgDB+gBSDVXctXiHxSQwJJOdUCRKN -yH4ErqFcpFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdD -aGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3Qg -Q0GCCD0MxODG3rn0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEANFYQ -R+T70VcZ+SnvURnwviFgCXeedBzCr21meo+DNHbkp2gudB9W8Xrned/wtUBVymy9 -gjB5jINfU7Lci0H57Evsw96UJJVfhXdUMHpqt1RGCoEd9FWnrDyrSy0NysnBT2bH -lEqxh3aFEUx9IOQ4sgnx1/NOFXBpkRtivl6O0Ec= +bmcgUm9vdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALpY5jb+ +S7AUbx9gzN06wkqUeb+eNLTjCOKofiMTn8Y0TqCA2ZyY3XMcNBMaIS7hdFTgmmqt +fFntYobxLAl/twfbz9AnRaVDh2HyUvHvMBxKn1HSDLALLtqdF0pcXIjP04S7NKPQ +Umgkv2H0KwcUpYlgjTFtXRiP+7wDSiQeP1YVSriEoE0TXK14F8np6ZKK0oQ+u16d +Wn3MGQwFzS+Ipgoz0jbi5D2KzmK2dzHdxY8M2Dktkz/W3DUfUwaTohYed2DG39LP +NUFOxekgXdIZ3vQbDfsEQt27TUzOztbo/BqK7YkRLzzOQFz+dKAxH6Hy6Bu9op7e +DWS9TfD/+UmDxr3IeoLMpmUBKxmzTC4qpej+W1UuCE12dMo4LoadlkG+/l1oABqd +Ucf45WgaFk3xpyEuGnDxjs6rqYPoEapIichxN2fgN+jkgH9ed44r0yOoVeG2pmwD +YFCCxzkmiuzLADlfM1LUzqUNKVFcOakD3iujHEalnDIJsc/znYsqaRvCkQIDAQAB +o4G7MIG4MB0GA1UdDgQWBBSDVXctXiHxSQwJJOdUCRKNyH4ErjCBiAYDVR0jBIGA +MH6AFINVdy1eIfFJDAkk51QJEo3IfgSuoVykWjBYMQswCQYDVQQGEwJVUzELMAkG +A1UECBMCSUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAW +BgNVBAMTD1Rlc3RpbmcgUm9vdCBDQYIIPQzE4MbeufQwDAYDVR0TBAUwAwEB/zAN +BgkqhkiG9w0BAQsFAAOCAYEAFIMFxLHaVDY/nsbYzI7+zxe4GJeUqRIj2g4XK/nF +6lHLRFL2YP5yJ+Jm4JDkoZqKq/tcEQLIssQS++s6tBSdvFwdY6imfpEZgFPuodrZ +KbYm4Xuouw09EQCEjPxBOQ1NEcPuwuDtvD6/BOfm3SRFRTq/gQwxKlZ7C/4l8b1+ +OQPIUryqdlFBpyE/M95GzaNdmkQx41PevEih2nqWnbTsXLeiSXLGoubMTxKEK4T+ +J7Ci2KTRJ3SYMgTNU6MNcl7b9Tpw9/KVG80IbpzNQ1LDh3ZtkOfqoou1lmBTeNPu +g2C/oiW6lVAmZx1TL9gbUtkJ0Q2iW4D9TF+zuYi2qpbVU3RvoqK25x3AuIWf4JOL +3cTNjJ/3zmGSORRJvcGyvVnL30R+vwpaxvyuqMjz3kBjkK2Z2pvElZMJiZhbGG7k +MHZQ5A26v0/iQVno6FRv3cQb9EeAZtNHcIEgsNhPZ53XVnwZ58ByvATMLKNN8dWF +Q+8Bbr7QFxeWvQfHYX2yaQZ/ -----END CERTIFICATE----- """ root_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- -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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----- -MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw +MIIEXDCCAsSgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQELBQAw WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw -DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw -ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh +DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMjAw +ODAyMTcxMTIwWhcNNDcxMjIwMTcxMTIwWjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT -MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK -FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT -21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID -AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX -QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+ -9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF -9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT +MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIIBojANBgkqhkiG9w0B +AQEFAAOCAY8AMIIBigKCAYEAo3rOxOVrdLdRsR1o0+JG7MRpCtMoafA63TM/DczL +Q4jURv5MzyTE7FFdXq4xNJRYgD16vUavZluQGj30+5Lkt07CuO/BK3itl8UW+dsH +p95gzBvgnj5AVZGkNOQ0Y4CbXO087Ywep7tpBfZ5fzURLeH+OHQGseEFZ5e0w8Az +AarWu+Ez5RGpkaZ61iiJa53mAgkrjw+o83UrpDT2nrXiyR6Fx4K4eb1rarodWqGv +jSkdT5MA4i0gDhsIBnTarPB+0KM8M7od8DkLsTHBt4rYYCHgCX1bWavzGlqPEw9h +ksK+LAbQKD9J2AxYDkL0PAeUuvWMhxEmN6hXePiw63sJzukRunAvut5A2+42JMkW +guDyqIvAjlCYcIyBvUbphP3qSFqww/hpZ2wh5UZOc1mzYJKR9MgI8/UhRJEJ7NyY +pF24EJbisjNE30ot8aM2/5cI5KevclcuPJWH8PjT/i1VnNpM4S8MqoPw6F+d75d/ +CtfI+LLfns4k3G9I+Qgxmpa5AgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJ +KoZIhvcNAQELBQADggGBAFVQ3Dmljrnbjys9ZIqcTs/B5ktKUAU2KNMO9TmoFymE +YhHKbCb5u/CnWq3jtBW6jgkQHrhfY9leUlH87BkB2o16BcSKjHknHZ2MCdEvQvOM +/nkkMDkOEoRn8mfCCxxgt8Kxf07wHDcnKoeJ3h9BXIl6nyJqJAcVWEJm1d75ayDG +0Kr0z+LcqMtQqYI0csK/XDQkunlE95qti1HzxW+JeAf6nRkr7RNZLtGmUGAMfyBK +9A0Db8QLR7O92YEmwoXtp+euN6uDdjw4A7KHjNXMdvqZoRfbZEA9c6XJTBj22h87 +gYUFRVpkNDrC/c9u6WgA943yMgFCwjrlTsmi+uoweT9U5r4TA+dVCDAv943aWCNm +A+TiuIXlJAHl2PlH7Umu/oMQKDEt+0n4QcQLBZyK3CYU5kg+ms9vOvE19Lhp8HeS +xqm6dwKpdm7/8EfGNW3s8Gm4KM26mb7dtSdHJFuR/BQ5y/cn4qIMyeGfHvsVew+2 +neyFR2Oc/nUlZMKfyHI+pA== -----END CERTIFICATE----- """ intermediate_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- """ server_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIICJDCCAY2gAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV +MIIEKTCCApGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHQ2hpY2FnbzEQMA4GA1UECgwH -VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTE3MDYxMjAwMTA1 -N1oXDTM3MDYwNzAwMTA1N1owGDEWMBQGA1UEAwwNbG92ZWx5IHNlcnZlcjCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvqb4brndXS2kEL84qXbZXE6LYK+UrhNi -70sdIM/24NVN7tXkPlOXqrMWhFHHml+aeSpPkH5b1vKnY1TcULmEubnNICtvjmZ5 -SGMQn+J+RmBs1SMd0EgY/0wBBQdlrlYp2QYgm8YC+zxTNSqWvhMFZAgHbj6Un5SS -T8JGBqytjB0CAwEAAaM2MDQwHQYDVR0OBBYEFINVdy1eIfFJDAkk51QJEo3IfgSu -MBMGA1UdJQQMMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEBBQUAA4GBAGki1K6WgHHJ -qC6aY2EowjaWOXLO6jUZIhGk7BA7vMRfNug429AOZ4m5F6OQhzmJmlw67Jyu2FeI -h0VtBuQoHPtjqZXF59oX6hMMmGLMs9pV0UA3fJs5MYA4/V5ZcQy0Ie0QoJNejLzE -6V1Qz1rRTYLUyEcpI7ZCmBg2KQQI8YZI +VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTIwMDgwMjE3MTEy +MFoXDTQ3MTIyMDE3MTEyMFowGDEWMBQGA1UEAwwNbG92ZWx5IHNlcnZlcjCCAaIw +DQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKU9txhKg6Nc0dVK9Vv4MYuYP6Hs +oR483+wC53V8axkfy2TynrBSug8HapeSFW5jwdwcsjaDwEIAugZfRoz0N1vR/Q6T +OFAYn2hRwlAgUXVk3NXpDNV/QRliGvxhLAVpvu1a4ExfVZoOQyPa8pogDgrUdB3e +tYmmFHNa09Lv1nyMZWi6t7zH2weq6/Dxpm0BWf+THFcunv9TNfAqmDV5qbxvaUPh +uvRpN+X2N3tejB8WKt+UmzAXUi3P3OgYimWXwq8Rmorc1rk5j+ksl6qYwZvi7rRV +g1ZAH7bGhXC9eEU/1Z9q26OhAPdTyJD0pc+G9vMz6VijLRXcgHBUP09lSeqxnNxc +pWoX6nRdGn6PkDhewHM05iqAE3ZHnc8kSBcRX85SoW5dGOhvvUTs4ePVNTo3vHdQ +vftTDD+I3rbFnYTKUAzHTPSWGE7LVEiWJ94RKSADXgve0qq8o377UMnY7W3UygSY +odyUZ29B5EfZ88EpIs/h5NomDv5VcQEoCWN1owIDAQABozYwNDAdBgNVHQ4EFgQU +g1V3LV4h8UkMCSTnVAkSjch+BK4wEwYDVR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZI +hvcNAQELBQADggGBACn0LsqO94tk8i+RbG5hryNduem9n8b8doYD97iaux6QLvY/ +A8DFduJtUevZ3OCsRYQSGa3V/ysMzN7/DIUkpRLevZmdw+1L6PGR7peR2xIQ+yEW +bL88vLjezaYIzMKHJRmN8oP3DQtGJm6U2fMMiEHWqRtULIVpnFppzPI2z7+tDeyg +PFD2YeiFWoq5lmXStrK+KYPJbhTn0gz4QlGBs7PLY2JMDRSVj6ctkvrpXbC3Rb3m +qo2FY/y51ACg77Txc6NAmNE6tCknwaUjRQP2MuoYFm5/Z6O9/g49AEVIE101zHqV +N6SkcTUaXAuQIyZaqwdndfOB4rrFyAkoxTM5OamIQl80hZKf4R5rM7D7Sz8kAWJi +BPIcewN0XnI6lm+zPAVUAE8dZfgJmJR5ifZHYCuv96EX0RpYsddeik8UmjkZ2/ch +vRzvRSNNxVC6Zoe6vKNUb89XMtJZqY80WxfWG3Z2Hwf9KvS+2KAH/6MiSMj0RI5F +SCB2PMQm6DYXwM1EyA== -----END CERTIFICATE----- """ -server_key_pem = normalize_privatekey_pem(b"""-----BEGIN RSA PRIVATE KEY----- -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== +server_key_pem = normalize_privatekey_pem( + b"""-----BEGIN RSA PRIVATE KEY----- +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 -----END RSA PRIVATE KEY----- -""") +""" +) intermediate_server_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw +MIIEXTCCAsWgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQELBQAw ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh -biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV +biBEaWVnbzAeFw0yMDA4MDIxNzExMjBaFw00NzEyMjAxNzExMjBaMG4xHTAbBgNV BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh -biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1 -iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4 -+kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm -biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw -UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN -3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6 -x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA== +biBEaWVnbzCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAL3UcTxwCsMZ +qIE+7lolm8t6lT0IYZkE4L7u2qI64m9CvztudqqKYZcrprZobZxqPhqc8IO3CFR2 +nVzwZWxrHCcm6nAzJjVXUFrc4TLsVYYJL1QvKXxr97VIiySU7x6xWrQQsqDtlrb0 +jH59EYFbM2eMk2fBT2X4h6YMXlqyrDjZF6apClXtkdxGJGqR5PCTs4cvrYW7TpIm +cuJq0S+MRBguZpriM+wOK7cXrqfRPFRzZtPXskpQPSAMDDAOGKl8OZfoVFYzG8KG +omOa0hcHtgYX2GCDs1g1maY6Haw9bgs041BoApH9aQxehy5dfU39DcFoKSE3dCjR +FaR6ryCA+f8L1F3xVaHsvX443CYF0/holfsptTjNd1T1z8WR5h1jtY0gJ/ERgcJZ +UgDRE3lEkTLExS/nuGVfdwnlkxny9jbtYp2YcjYjUkChLtTgz4ommeIdBdDvSu8M +wWHMtQNxECs5qA5J384cLh11Nd9exWUjiQ9yAZ0qTOzTkdH7VPHfxQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBgQA2jC5hJ/+46RLBuaZiJhBawiY+HqybEAZWM/IBGZO4 +UKcRotovU+sb1jg+vpXwePSBPEtQoZce0jN0TKiCdlLM4/9lybAvc6qBLJ0d4VS5 +BU5QsCs9IKyvswAFVipQZi0szYwHk8T145SH/fPao8oznf5ae4a6rK9PyZqT7Ix1 +nnKGffbJs0dY+jlxmx/BPlbsGfTwPL6LexghjvbpbXWUdVLP3gAW6DPCtRd6lhWj +JvgCkF2SnbQ7GgnPEYi8h09j0c6/sK6jLoNAatJyIlRGE1cdGYZVUlVW/xP6lYM0 +Mi1KKl0ZXOne4vPTtnTBBqrpjdLydH3WM1IxdwSRbmF15OD6BWzzKV4IYUJ21GDh +YrVrcIeN49pUoKVTTn0Sql8f8mXxJhJ54wo9TKdIGZeuwTZrfWjcjWghXgghXGoP +RI/I5fk/OMu0Oc06/+xdwCBHCSge0/vxK6fhTu7PxmJhQcZF0sDZyb6LXm2feVkG +6FsxnsvstVNO3oJdpa8daLs= -----END CERTIFICATE----- """ intermediate_server_key_pem = b"""-----BEGIN RSA PRIVATE KEY----- -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 -----END RSA PRIVATE KEY----- """ client_cert_pem = b"""-----BEGIN CERTIFICATE----- -MIICIjCCAYugAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV +MIIEJzCCAo+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIDAJJTDEQMA4GA1UEBwwHQ2hpY2FnbzEQMA4GA1UECgwH -VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTE3MDYxMjAwMDQx -M1oXDTM3MDYwNzAwMDQxM1owFjEUMBIGA1UEAwwLdWdseSBjbGllbnQwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAMBmH9JG02bme0xPipvpjMSlOugyWrauf4at -EdGJn7GQLD8IY2Fu0+Kvv9DFpSPboFKZCsfDVsYoRs+xaJbtt1dJ6ymX7EqKS7gb -8q+eeZZ14keqyJd5Rm2q6swQtw9ADD3E8cS6GqpQm+8SgxOycISoYz7sO1ugJFqN -jId+W4BFAgMBAAGjNjA0MB0GA1UdDgQWBBSDVXctXiHxSQwJJOdUCRKNyH4ErjAT -BgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUFAAOBgQAMqcHyweaCOZNN -dWQQOsBKQlL5wqVVZwucHPWqobjxpULKy9gS2ha2zbgkXcB/BnBOSwe0Fm+MJV0T -NbnTghcGJNpEH7VKn4xSLvIGZmnZZWgxeIB16z4GhpkK2fShBJ+6GKZjsgjT0lSH -JRgjHbWutZfZvbSHXr9n7PIphG1Ojg== +VGVzdGluZzEYMBYGA1UEAwwPVGVzdGluZyBSb290IENBMB4XDTIwMDgwMjE3MTEy +MVoXDTQ3MTIyMDE3MTEyMVowFjEUMBIGA1UEAwwLdWdseSBjbGllbnQwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDGChdOMY164FScJqfiJ5LEtjYOKEg4 +nmMAMGuHIT8wZZEfzaaHhBbypzKq2cPP1qtyHgvtUMM6KOFEj4y9AonqzzdlVxbM +i6+AvYLWlPoB5r/G1GdslUvXbc7F02B/6sB/+iFXmcdjOjAQcLWxVgUL+1CoeoY1 +awNYmzQueK/T82a/6AYTdrx7XRX4wfxjYb1o3bnnRD/jGSakIylXeUGFsiSNkBs/ +dJMkUONxizAdAE2tW6NhPuE2O0UipzUhdgFnH6WPfJ0J1S7jZ3eQTUrLkFpWSp/Z +hx/l/Ql9vO0wagHaT2wOiZdKVT8S6V6OPzJ7/H1evCoM6EuSPBC5DDP1nPetCK1v +uC9kb7Dg6yFPt1CKrVFt0Y6W5Y5/GzisUtvYV/OGtX4DOwL9It68D04Qrvun1t/a +Dh/c5gKqfqIGHUvUucFmOi6DrRpadfraLZMRGN2ysPjoVwhMgwwSmSWhziQIUfxK +oyz1CUsyr5Gh5gdifbe1AOYwu6YdtlmhqCsCAwEAAaM2MDQwHQYDVR0OBBYEFINV +dy1eIfFJDAkk51QJEo3IfgSuMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA0GCSqGSIb3 +DQEBCwUAA4IBgQAhAEACc1j6EYoSfVJD8N/FlYfHRizdfVJyrmMnC8ID1vtfrU2z +S2q+49ja2NyM4Sq+Cf+i+sFfzFG92LayZt9Mc1BnHZMdNzQL7Ynr2nDLxHsHzuYa +N21/ucTpHEFGLmvQ/eWBMxQQ9TbiNXn+tnnqg46dRzN3vHJp+g5+ijtMcuh007z2 +niiO8F07wlb960XviejWejMC8hBLWlA7i3EjAkDO8RFQnG2Py5cQX9GgmWH1sDy3 +rIsWlU+e46ysSWK/bnudnAlzZMB9KJATVZu5+xmCumH2hLJv5vz+jnKcgU9MBZMO +cKgNdFUbtRlU/gfTaohmLIuSquunCCrXLsLD8ygbKKXfSPGVo2XkvX3oxqUo6dmA +LvU4N4sCQGiSzW+a13HBtk3TBZFsJSWUGSW/H7TVFiAonumJKRqRxMOkkB9JxX+V +9LZBYuBLpOeK4wZ8BUSNlHKnGpDzl0DzdYrGlzWz0jXlLGZ8KMfXAn9h0mOZ+IyK +eUlgMBYyAspCQzM= -----END CERTIFICATE----- """ -client_key_pem = normalize_privatekey_pem(b"""-----BEGIN RSA PRIVATE KEY----- -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== +client_key_pem = normalize_privatekey_pem( + b"""-----BEGIN RSA PRIVATE KEY----- +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 -----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 @@ -321,6 +516,23 @@ 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 @@ -361,7 +573,8 @@ -----END PKCS7----- """ -pkcs7DataASN1 = base64.b64decode(b""" +pkcs7DataASN1 = base64.b64decode( + b""" MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN @@ -380,7 +593,8 @@ VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG /g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 Ho4EzbYCOaEAMQA= -""") +""" +) crlData = b"""\ -----BEGIN X509 CRL----- @@ -563,6 +777,12 @@ Td8GMrwKz0557OxxtKN6uVVy4ACFMqEw0zN/KJI1vxc9 -----END CERTIFICATE-----""" +rsa_p_not_prime_pem = """ +-----BEGIN RSA PRIVATE KEY----- +MBsCAQACAS0CAQcCAQACAQ8CAQMCAQACAQACAQA= +-----END RSA PRIVATE KEY----- +""" + @pytest.fixture def x509_data(): @@ -602,8 +822,8 @@ # 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): @@ -612,30 +832,40 @@ """ 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') + basic = X509Extension(b"basicConstraints", True, b"CA:true") assert isinstance(basic, X509Extension) - comment = X509Extension(b'nsComment', False, b'pyOpenSSL unit test') + 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') - ]) + @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 @@ -644,19 +874,19 @@ 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 @@ -670,9 +900,9 @@ `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): """ @@ -681,13 +911,14 @@ """ 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): """ @@ -696,11 +927,12 @@ """ 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): """ @@ -708,14 +940,9 @@ is given no value, something happens. """ with pytest.raises(Error): - X509Extension(b'subjectKeyIdentifier', False, b'hash') + X509Extension(b"subjectKeyIdentifier", False, b"hash") - @pytest.mark.parametrize('bad_obj', [ - True, - object(), - "hello", - [], - ]) + @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 @@ -723,7 +950,8 @@ """ 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): """ @@ -732,12 +960,13 @@ """ 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): """ @@ -746,13 +975,13 @@ """ 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): """ @@ -761,15 +990,10 @@ """ with pytest.raises(Error): X509Extension( - b'authorityKeyIdentifier', - False, b'keyid:always,issuer:always') + b"authorityKeyIdentifier", False, b"keyid:always,issuer:always" + ) - @pytest.mark.parametrize('bad_obj', [ - True, - object(), - "hello", - [], - ]) + @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 @@ -777,8 +1001,11 @@ """ 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): @@ -836,7 +1063,7 @@ """ PKey.to_cryptography_key creates a proper cryptography private key. """ - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + pkey = load_privatekey(FILETYPE_PEM, root_key_pem) key = pkey.to_cryptography_key() assert isinstance(key, rsa.RSAPrivateKey) @@ -846,7 +1073,7 @@ """ `PKey` can be used to create instances of that type. """ - assert is_consistent_type(PKey, 'PKey') + assert is_consistent_type(PKey, "PKey") def test_construction(self): """ @@ -968,6 +1195,14 @@ 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): """ @@ -980,6 +1215,7 @@ # 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) @@ -1087,6 +1323,7 @@ """ `X509Name` instances should compare based on their NIDs. """ + def _equality(a, b, assert_true, assert_false): assert_true(a == b) assert_false(a != b) @@ -1110,30 +1347,28 @@ 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()) @@ -1153,8 +1388,7 @@ # 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) @@ -1162,8 +1396,7 @@ # 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): """ @@ -1180,9 +1413,10 @@ `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): """ @@ -1213,8 +1447,8 @@ 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): """ @@ -1283,7 +1517,7 @@ 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. @@ -1308,7 +1542,7 @@ """ `X509Req` can be used to create instances of that type. """ - assert is_consistent_type(X509Req, 'X509Req') + assert is_consistent_type(X509Req, "X509Req") def test_construction(self): """ @@ -1360,13 +1594,14 @@ 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): """ @@ -1376,17 +1611,23 @@ 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' + assert exts[1].get_data() == b"\x03\x02\x07\x80" + # Requesting it a second time should return the same list + exts = request.get_extensions() + assert len(exts) == 2 def test_add_extensions_wrong_args(self): """ @@ -1426,7 +1667,7 @@ key which signed the request. """ request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + pkey = load_privatekey(FILETYPE_PEM, root_key_pem) request.sign(pkey, GOOD_DIGEST) another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) with pytest.raises(Error): @@ -1438,7 +1679,7 @@ which represents the public part of the key which signed the request. """ request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + pkey = load_privatekey(FILETYPE_PEM, root_key_pem) request.sign(pkey, GOOD_DIGEST) assert request.verify(pkey) @@ -1465,28 +1706,8 @@ """ Tests for `OpenSSL.crypto.X509`. """ - pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM - 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----- - """ + pemData = root_cert_pem + root_key_pem def signable(self): """ @@ -1498,7 +1719,7 @@ """ `X509` can be used to create instances of that type. """ - assert is_consistent_type(X509, 'X509') + assert is_consistent_type(X509, "X509") def test_construction(self): """ @@ -1506,7 +1727,7 @@ """ certificate = X509() assert isinstance(certificate, X509) - assert type(certificate).__name__ == 'X509' + assert type(certificate).__name__ == "X509" assert type(certificate) == X509 def test_set_version_wrong_args(self): @@ -1553,8 +1774,8 @@ 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 @@ -1638,8 +1859,8 @@ 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( @@ -1664,8 +1885,8 @@ 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( @@ -1712,11 +1933,15 @@ # 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" @@ -1725,9 +1950,10 @@ 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): """ @@ -1735,10 +1961,11 @@ 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, []) @@ -1758,27 +1985,28 @@ `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) @@ -1796,13 +2024,14 @@ 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): """ @@ -1820,10 +2049,13 @@ 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): """ @@ -1841,12 +2073,13 @@ """ 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): """ @@ -1856,10 +2089,13 @@ 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): """ @@ -1877,12 +2113,13 @@ """ 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): """ @@ -1917,7 +2154,7 @@ the algorithm used to sign the certificate. """ cert = load_certificate(FILETYPE_PEM, self.pemData) - assert b"sha1WithRSAEncryption" == cert.get_signature_algorithm() + assert b"sha256WithRSAEncryption" == cert.get_signature_algorithm() def test_get_undefined_signature_algorithm(self): """ @@ -1989,17 +2226,17 @@ """ `X509Store` is a type object. """ - assert is_consistent_type(X509Store, 'X509Store') + 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, cleartextCertificatePEM) + cert = load_certificate(FILETYPE_PEM, root_cert_pem) 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 @@ -2014,23 +2251,84 @@ `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, cleartextCertificatePEM) + cert = load_certificate(FILETYPE_PEM, root_cert_pem) store = X509Store() store.add_cert(cert) store.add_cert(cert) + @pytest.mark.parametrize( + "cafile, capath, call_cafile, call_capath", + [ + ( + "/cafile" + NON_ASCII, + None, + b"/cafile" + NON_ASCII.encode(sys.getfilesystemencoding()), + _ffi.NULL, + ), + ( + b"/cafile" + NON_ASCII.encode("utf-8"), + None, + b"/cafile" + NON_ASCII.encode("utf-8"), + _ffi.NULL, + ), + ( + None, + "/capath" + NON_ASCII, + _ffi.NULL, + b"/capath" + NON_ASCII.encode(sys.getfilesystemencoding()), + ), + ( + None, + b"/capath" + NON_ASCII.encode("utf-8"), + _ffi.NULL, + b"/capath" + NON_ASCII.encode("utf-8"), + ), + ], + ) + def test_load_locations_parameters( + self, cafile, capath, call_cafile, call_capath, monkeypatch + ): + class LibMock(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. """ - assert is_consistent_type(PKCS12, 'PKCS12') + assert is_consistent_type(PKCS12, "PKCS12") def test_empty_construction(self): """ @@ -2053,13 +2351,13 @@ 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) @@ -2070,7 +2368,7 @@ """ passwd = b"blah" p12 = PKCS12() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + pkey = load_privatekey(FILETYPE_PEM, root_key_pem) p12.set_privatekey(pkey) assert None is p12.get_certificate() assert pkey == p12.get_privatekey() @@ -2096,7 +2394,7 @@ """ passwd = b"blah" p12 = PKCS12() - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) + cert = load_certificate(FILETYPE_PEM, root_cert_pem) p12.set_certificate(cert) assert cert == p12.get_certificate() assert None is p12.get_privatekey() @@ -2119,12 +2417,13 @@ # it to. At some point, hopefully this will change so that # p12.get_certificate() is actually what returns the loaded # certificate. - assert ( - cleartextCertificatePEM == - dump_certificate(FILETYPE_PEM, p12.get_ca_certificates()[0])) + assert root_cert_pem == 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. @@ -2146,27 +2445,48 @@ 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): """ @@ -2178,9 +2498,11 @@ """ 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): """ @@ -2195,7 +2517,7 @@ b"-export", b"-clcerts", b"-passout", - b"pass:" + passwd + b"pass:" + passwd, ) p12 = load_pkcs12(p12_str, passphrase=passwd) self.verify_pkcs12_container(p12) @@ -2208,15 +2530,21 @@ """ 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")) - assert ( - "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message)) + msg = "{0} for passphrase is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) + assert msg == str(w[-1].message) self.verify_pkcs12_container(p12) @@ -2228,7 +2556,8 @@ """ 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) @@ -2247,7 +2576,8 @@ 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): """ @@ -2256,7 +2586,8 @@ 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): """ @@ -2265,7 +2596,8 @@ 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): """ @@ -2274,17 +2606,18 @@ 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 = 'whatever' + passwd = b"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): @@ -2314,7 +2647,7 @@ """ 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) @@ -2325,8 +2658,12 @@ # 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): """ @@ -2340,8 +2677,12 @@ 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): """ @@ -2360,8 +2701,12 @@ 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): """ @@ -2387,14 +2732,14 @@ """ 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): """ @@ -2403,7 +2748,8 @@ 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): """ @@ -2414,15 +2760,15 @@ with pytest.warns(DeprecationWarning) as w: simplefilter("always") dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii")) - assert ( - "{0} for passphrase is no longer accepted, use bytes".format( - WARNING_TYPE_EXPECTED - ) == str(w[-1].message)) + msg = "{0} for passphrase is no longer accepted, use bytes".format( + WARNING_TYPE_EXPECTED + ) + assert msg == 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): @@ -2453,6 +2799,7 @@ """ Tests for :func:`load_publickey`. """ + def test_loading_works(self): """ load_publickey loads public keys and sets correct attributes. @@ -2481,7 +2828,7 @@ """ 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) @@ -2507,7 +2854,8 @@ """ with pytest.raises(TypeError): load_privatekey( - FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object()) + FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object() + ) def test_load_privatekey_wrongPassphrase(self): """ @@ -2524,7 +2872,7 @@ with a private key encoded in a format, that doesn't support encryption. """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) blob = dump_privatekey(FILETYPE_ASN1, key) with pytest.raises(ValueError): load_privatekey(FILETYPE_ASN1, blob, "secret") @@ -2535,8 +2883,10 @@ string if given the passphrase. """ key = load_privatekey( - FILETYPE_PEM, encryptedPrivateKeyPEM, - encryptedPrivateKeyPEMPassphrase) + FILETYPE_PEM, + encryptedPrivateKeyPEM, + encryptedPrivateKeyPEMPassphrase, + ) assert isinstance(key, PKey) def test_load_privatekey_passphrase_exception(self): @@ -2544,6 +2894,7 @@ If the passphrase callback raises an exception, that exception is raised by `load_privatekey`. """ + def cb(ignored): raise ArithmeticError @@ -2561,6 +2912,7 @@ def cb(*a): called.append(None) return b"quack" + with pytest.raises(Error) as err: load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) assert called @@ -2577,6 +2929,7 @@ def cb(writing): called.append(writing) return encryptedPrivateKeyPEMPassphrase + key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) assert isinstance(key, PKey) assert called == [False] @@ -2588,7 +2941,8 @@ """ 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): """ @@ -2649,6 +3003,7 @@ `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 @@ -2660,9 +3015,9 @@ `dump_privatekey` writes an encrypted PEM when given a passphrase. """ passphrase = b"foo" - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase) - assert isinstance(pem, binary_type) + assert isinstance(pem, bytes) loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) assert isinstance(loadedKey, PKey) assert loadedKey.type() == key.type() @@ -2674,7 +3029,7 @@ with a private key encoded in a format, that doesn't support encryption. """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ValueError): dump_privatekey(FILETYPE_ASN1, key, GOOD_CIPHER, "secret") @@ -2682,27 +3037,25 @@ """ `dump_certificate` writes PEM, DER, and text. """ - pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM + pemData = root_cert_pem + root_key_pem cert = load_certificate(FILETYPE_PEM, pemData) dumped_pem = dump_certificate(FILETYPE_PEM, cert) - assert dumped_pem == cleartextCertificatePEM + assert dumped_pem == root_cert_pem dumped_der = dump_certificate(FILETYPE_ASN1, cert) good_der = _runopenssl(dumped_pem, b"x509", b"-outform", b"DER") assert dumped_der == good_der cert2 = load_certificate(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_certificate(FILETYPE_PEM, cert2) - assert dumped_pem2 == cleartextCertificatePEM + assert dumped_pem2 == root_cert_pem dumped_text = dump_certificate(FILETYPE_TEXT, cert) - good_text = _runopenssl( - dumped_pem, b"x509", b"-noout", b"-text", b"-nameopt", b"") - assert dumped_text == good_text + assert len(dumped_text) > 500 def test_dump_certificate_bad_type(self): """ `dump_certificate` raises a `ValueError` if it's called with a bad type. """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) + cert = load_certificate(FILETYPE_PEM, root_cert_pem) with pytest.raises(ValueError): dump_certificate(object(), cert) @@ -2710,36 +3063,35 @@ """ `dump_privatekey` writes a PEM """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) assert key.check() dumped_pem = dump_privatekey(FILETYPE_PEM, key) - assert dumped_pem == cleartextPrivateKeyPEM + assert dumped_pem == normalized_root_key_pem def test_dump_privatekey_asn1(self): """ `dump_privatekey` writes a DER """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) + key = load_privatekey(FILETYPE_PEM, root_key_pem) dumped_der = dump_privatekey(FILETYPE_ASN1, key) - # 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 + assert dumped_der == root_key_der + + def test_load_privatekey_asn1(self): + """ + `dump_privatekey` writes a DER + """ + key = load_privatekey(FILETYPE_ASN1, root_key_der) + assert key.bits() == 3072 + assert key.type() == TYPE_RSA def test_dump_privatekey_text(self): """ `dump_privatekey` writes a text """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - + key = load_privatekey(FILETYPE_PEM, root_key_pem) dumped_text = dump_privatekey(FILETYPE_TEXT, key) - good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text") - assert dumped_text == good_text + assert len(dumped_text) > 500 def test_dump_publickey_pem(self): """ @@ -2773,7 +3125,8 @@ `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) @@ -2783,9 +3136,7 @@ dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2) assert dumped_pem2 == cleartextCertificateRequestPEM dumped_text = dump_certificate_request(FILETYPE_TEXT, req) - good_text = _runopenssl( - dumped_pem, b"req", b"-noout", b"-text", b"-nameopt", b"") - assert dumped_text == good_text + assert len(dumped_text) > 500 with pytest.raises(ValueError): dump_certificate_request(100, req) @@ -2800,9 +3151,10 @@ def cb(writing): called.append(writing) return passphrase - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + + key = load_privatekey(FILETYPE_PEM, root_key_pem) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) - assert isinstance(pem, binary_type) + assert isinstance(pem, bytes) assert called == [True] loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) assert isinstance(loadedKey, PKey) @@ -2814,10 +3166,11 @@ `dump_privatekey` should not overwrite the exception raised by the passphrase callback. """ + def cb(ignored): raise ArithmeticError - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ArithmeticError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) @@ -2826,13 +3179,52 @@ `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, cleartextPrivateKeyPEM) + key = load_privatekey(FILETYPE_PEM, root_key_pem) with pytest.raises(ValueError): dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) + def test_dump_privatekey_truncated(self): + """ + `crypto.dump_privatekey` should not truncate a passphrase that contains + a null byte. + """ + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + passphrase = b"foo\x00bar" + truncated_passphrase = passphrase.split(b"\x00", 1)[0] + + # By dumping with the full passphrase load should raise an error if we + # try to load using the truncated passphrase. If dump truncated the + # passphrase, then we WILL load the privatekey and the test fails + encrypted_key_pem = dump_privatekey( + FILETYPE_PEM, key, "AES-256-CBC", passphrase + ) + with pytest.raises(Error): + load_privatekey( + FILETYPE_PEM, encrypted_key_pem, truncated_passphrase + ) + + def test_load_privatekey_truncated(self): + """ + `crypto.load_privatekey` should not truncate a passphrase that contains + a null byte. + """ + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + passphrase = b"foo\x00bar" + truncated_passphrase = passphrase.split(b"\x00", 1)[0] + + # By dumping using the truncated passphrase load should raise an error + # if we try to load using the full passphrase. If load truncated the + # passphrase, then we WILL load the privatekey and the test fails + encrypted_key_pem = dump_privatekey( + FILETYPE_PEM, key, "AES-256-CBC", truncated_passphrase + ) + with pytest.raises(Error): + load_privatekey(FILETYPE_PEM, encrypted_key_pem, passphrase) + def test_load_pkcs7_data_pem(self): """ `load_pkcs7_data` accepts a PKCS#7 string and returns an instance of @@ -2931,7 +3323,7 @@ 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): """ @@ -2958,7 +3350,7 @@ """ `NetscapeSPKI` can be used to create instances of that type. """ - assert is_consistent_type(NetscapeSPKI, 'NetscapeSPKI') + assert is_consistent_type(NetscapeSPKI, "NetscapeSPKI") def test_construction(self): """ @@ -2982,13 +3374,14 @@ """ nspki = NetscapeSPKI() blob = nspki.b64_encode() - assert isinstance(blob, binary_type) + assert isinstance(blob, bytes) 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 @@ -2998,7 +3391,7 @@ 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) @@ -3015,7 +3408,7 @@ 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 @@ -3025,17 +3418,17 @@ `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) @@ -3066,15 +3459,15 @@ 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 @@ -3091,24 +3484,27 @@ """ 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, cleartextCertificatePEM) - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + + cert = load_certificate(FILETYPE_PEM, root_cert_pem) + pkey = load_privatekey(FILETYPE_PEM, root_key_pem) root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) root_key = load_privatekey(FILETYPE_PEM, root_key_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) intermediate_key = load_privatekey(FILETYPE_PEM, intermediate_key_pem) intermediate_server_cert = load_certificate( - FILETYPE_PEM, intermediate_server_cert_pem) + 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): """ @@ -3127,8 +3523,8 @@ 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 @@ -3145,13 +3541,17 @@ 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): """ @@ -3168,17 +3568,18 @@ 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 @@ -3187,19 +3588,11 @@ """ 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 text == dumped_text + assert len(dumped_text) > 500 def test_export_custom_digest(self): """ @@ -3209,7 +3602,7 @@ 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): """ @@ -3222,7 +3615,7 @@ 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): """ @@ -3288,7 +3681,8 @@ 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): """ @@ -3300,18 +3694,18 @@ 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 @@ -3323,19 +3717,19 @@ 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): """ @@ -3360,7 +3754,7 @@ """ 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): """ @@ -3383,15 +3777,15 @@ # 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): @@ -3403,17 +3797,20 @@ 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): """ @@ -3424,15 +3821,17 @@ 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) @@ -3453,10 +3852,12 @@ """ 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): """ @@ -3481,6 +3882,145 @@ 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 @@ -3501,8 +4041,8 @@ 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): """ @@ -3516,8 +4056,8 @@ 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): """ @@ -3531,8 +4071,8 @@ 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): """ @@ -3549,8 +4089,8 @@ 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 @@ -3566,7 +4106,7 @@ 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) @@ -3574,7 +4114,106 @@ with pytest.raises(X509StoreContextError) as exc: store_ctx.verify_certificate() - assert exc.value.args[0][2] == 'certificate has expired' + 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" class TestSignVerify(object): @@ -3591,7 +4230,8 @@ 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) @@ -3600,7 +4240,7 @@ # 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 @@ -3639,22 +4279,20 @@ 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): """ @@ -3668,7 +4306,7 @@ 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") @@ -3693,7 +4331,8 @@ 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") @@ -3765,6 +4404,7 @@ """ Tests `_EllipticCurve`'s implementation of ``==`` and ``!=``. """ + curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: @@ -3789,6 +4429,7 @@ 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: @@ -3809,7 +4450,7 @@ 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 -Nru pyopenssl-19.1.0/tests/test_rand.py pyopenssl-20.0.1/tests/test_rand.py --- pyopenssl-19.1.0/tests/test_rand.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/test_rand.py 2020-12-15 15:30:54.000000000 +0000 @@ -11,11 +11,7 @@ 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 @@ -28,7 +24,7 @@ """ `OpenSSL.rand.add` adds entropy to the PRNG. """ - rand.add(b'hamburger', 3) + rand.add(b"hamburger", 3) def test_status(self): """ diff -Nru pyopenssl-19.1.0/tests/test_ssl.py pyopenssl-20.0.1/tests/test_ssl.py --- pyopenssl-19.1.0/tests/test_ssl.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/test_ssl.py 2020-12-15 15:30:54.000000000 +0000 @@ -6,12 +6,19 @@ """ import datetime +import gc import sys import uuid from gc import collect, get_referrers from errno import ( - EAFNOSUPPORT, ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN) + EAFNOSUPPORT, + ECONNREFUSED, + EINPROGRESS, + EWOULDBLOCK, + EPIPE, + ESHUTDOWN, +) from sys import platform, getfilesystemencoding from socket import AF_INET, AF_INET6, MSG_PEEK, SHUT_RDWR, error, socket from os import makedirs @@ -45,58 +52,103 @@ 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, Session, Connection, 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, + NO_OVERLAPPING_PROTOCOLS, +) 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 ( - cleartextCertificatePEM, cleartextPrivateKeyPEM, - client_cert_pem, client_key_pem, server_cert_pem, server_key_pem, - root_cert_pem) + client_cert_pem, + client_key_pem, + server_cert_pem, + server_key_pem, + root_cert_pem, + root_key_pem, +) -# openssl dhparam 1024 -out dh-1024.pem (note that 1024 is a small number of -# bits to use) +# openssl dhparam 2048 -out dh-2048.pem dhparam = """\ -----BEGIN DH PARAMETERS----- -MIGHAoGBALdUMvn+C9MM+y5BWZs11mSeH6HHoEq0UVbzVq7UojC1hbsZUuGukQ3a -Qh2/pwqb18BZFykrWB0zv/OkLa0kx4cuUgNrUVq1EFheBiX6YqryJ7t2sO09NQiO -V7H54LmltOT/hEh6QWsJqb6BQgH65bswvV/XkYGja8/T0GzvbaVzAgEC +MIIBCAKCAQEA2F5e976d/GjsaCdKv5RMWL/YV7fq1UUWpPAer5fDXflLMVUuYXxE +3m3ayZob9lbpgEU0jlPAsXHfQPGxpKmvhv+xV26V/DEoukED8JeZUY/z4pigoptl ++8+TYdNNE/rFSZQFXIp+v2D91IEgmHBnZlKFSbKR+p8i0KjExXGjU6ji3S5jkOku +ogikc7df1Ui0hWNJCmTjExq07aXghk97PsdFSxjdawuG3+vos5bnNoUwPLYlFc/z +ITYG0KXySiCLi4UDlXTZTz7u/+OYczPEgqa/JPUddbM/kfvaRAnjY38cfQ7qXf8Y +i5s5yYK7a/0eWxxRr2qraYaUj8RwDpH9CwIBAg== -----END DH PARAMETERS----- """ @@ -148,7 +200,7 @@ """ # Connect a pair of sockets port = socket_any_family() - port.bind(('', 0)) + port.bind(("", 0)) port.listen(1) client = socket(port.family) client.setblocking(False) @@ -191,47 +243,53 @@ 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') + caext = X509Extension(b"basicConstraints", False, b"CA:true") + not_after_date = datetime.date.today() + datetime.timedelta(days=365) + not_after = not_after_date.strftime("%Y%m%d%H%M%SZ").encode("ascii") # Step 1 cakey = PKey() - cakey.generate_key(TYPE_RSA, 1024) + cakey.generate_key(TYPE_RSA, 2048) cacert = X509() + cacert.set_version(2) cacert.get_subject().commonName = "Authority Certificate" cacert.set_issuer(cacert.get_subject()) cacert.set_pubkey(cakey) cacert.set_notBefore(b"20000101000000Z") - cacert.set_notAfter(b"20200101000000Z") + cacert.set_notAfter(not_after) cacert.add_extensions([caext]) cacert.set_serial_number(0) - cacert.sign(cakey, "sha1") + cacert.sign(cakey, "sha256") # Step 2 ikey = PKey() - ikey.generate_key(TYPE_RSA, 1024) + ikey.generate_key(TYPE_RSA, 2048) icert = X509() + icert.set_version(2) icert.get_subject().commonName = "Intermediate Certificate" icert.set_issuer(cacert.get_subject()) icert.set_pubkey(ikey) icert.set_notBefore(b"20000101000000Z") - icert.set_notAfter(b"20200101000000Z") + icert.set_notAfter(not_after) icert.add_extensions([caext]) icert.set_serial_number(0) - icert.sign(cakey, "sha1") + icert.sign(cakey, "sha256") # Step 3 skey = PKey() - skey.generate_key(TYPE_RSA, 1024) + skey.generate_key(TYPE_RSA, 2048) scert = X509() + scert.set_version(2) scert.get_subject().commonName = "Server Certificate" scert.set_issuer(icert.get_subject()) scert.set_pubkey(skey) scert.set_notBefore(b"20000101000000Z") - scert.set_notAfter(b"20200101000000Z") - scert.add_extensions([ - X509Extension(b'basicConstraints', True, b'CA:false')]) + scert.set_notAfter(not_after) + scert.add_extensions( + [X509Extension(b"basicConstraints", True, b"CA:false")] + ) scert.set_serial_number(0) - scert.sign(ikey, "sha1") + scert.sign(ikey, "sha256") return [(cakey, cacert), (ikey, icert), (skey, scert)] @@ -288,8 +346,10 @@ # 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: @@ -339,6 +399,7 @@ 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 @@ -352,8 +413,13 @@ 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) @@ -366,31 +432,29 @@ 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") @@ -406,19 +470,20 @@ @pytest.fixture def context(): """ - A simple TLS 1.0 context. + A simple "best TLS you can get" context. TLS 1.2+ in any reasonable OpenSSL """ - return Context(TLSv1_METHOD) + return Context(SSLv23_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 @@ -448,14 +513,14 @@ 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', - ), - ], - ) + [ + ( + "SSL routines", + "SSL_CTX_set_cipher_list", + "no cipher match", + ) + ], + ) def test_load_client_ca(self, context, ca_file): """ @@ -479,9 +544,7 @@ """ 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): """ @@ -497,9 +560,11 @@ 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): @@ -537,15 +602,15 @@ """ `Context` can be used to create instances of that type. """ - assert is_consistent_type(Context, 'Context', TLSv1_METHOD) + 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, 512) - ctx = Context(TLSv1_METHOD) + key.generate_key(TYPE_RSA, 1024) + ctx = Context(SSLv23_METHOD) ctx.use_privatekey(key) with pytest.raises(TypeError): ctx.use_privatekey("") @@ -555,7 +620,7 @@ `Context.use_privatekey_file` raises `OpenSSL.SSL.Error` when passed the name of a file which does not exist. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(Error): ctx.use_privatekey_file(tmpfile) @@ -565,23 +630,21 @@ arguments does not raise an exception. """ key = PKey() - key.generate_key(TYPE_RSA, 512) + key.generate_key(TYPE_RSA, 1024) 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(TLSv1_METHOD) + ctx = Context(SSLv23_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(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.use_privatekey_file(tmpfile, filetype) @@ -610,7 +673,7 @@ `Context.use_certificate_wrong_args` raises `TypeError` when not passed exactly one `OpenSSL.crypto.X509` instance as an argument. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.use_certificate("hello, world") @@ -620,7 +683,7 @@ `OpenSSL.crypto.X509` instance which has not been initialized (ie, which does not actually have any certificate data). """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(Error): ctx.use_certificate(X509()) @@ -633,17 +696,15 @@ # 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(TLSv1_METHOD) - ctx.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - ) + ctx = Context(SSLv23_METHOD) + ctx.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) def test_use_certificate_file_wrong_args(self): """ `Context.use_certificate_file` raises `TypeError` if the first argument is not a byte string or the second argument is not an integer. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.use_certificate_file(object(), FILETYPE_PEM) with pytest.raises(TypeError): @@ -656,7 +717,7 @@ `Context.use_certificate_file` raises `OpenSSL.SSL.Error` if passed the name of a file which does not exist. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(Error): ctx.use_certificate_file(tmpfile) @@ -670,9 +731,9 @@ # 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(cleartextCertificatePEM) + pem_file.write(root_cert_pem) - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) ctx.use_certificate_file(certificate_file) def test_use_certificate_file_bytes(self, tmpfile): @@ -700,7 +761,7 @@ """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, client_cert_pem) - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.use_privatekey(key) context.use_certificate(cert) assert None is context.check_privatekey() @@ -713,7 +774,7 @@ """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.use_privatekey(key) context.use_certificate(cert) with pytest.raises(Error): @@ -725,7 +786,7 @@ using `Context.get_app_data`. """ app_data = object() - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_app_data(app_data) assert context.get_app_data() is app_data @@ -734,7 +795,7 @@ `Context.set_options` raises `TypeError` if called with a non-`int` argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_options(None) @@ -742,7 +803,7 @@ """ `Context.set_options` returns the new options value. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) options = context.set_options(OP_NO_SSLv2) assert options & OP_NO_SSLv2 == OP_NO_SSLv2 @@ -751,7 +812,7 @@ `Context.set_mode` raises `TypeError` if called with a non-`int` argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_mode(None) @@ -760,7 +821,7 @@ `Context.set_mode` accepts a mode bitvector and returns the newly set mode. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) assert MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS) def test_set_timeout_wrong_args(self): @@ -768,7 +829,7 @@ `Context.set_timeout` raises `TypeError` if called with a non-`int` argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_timeout(None) @@ -778,7 +839,7 @@ created using the context object. `Context.get_timeout` retrieves this value. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_timeout(1234) assert context.get_timeout() == 1234 @@ -787,7 +848,7 @@ `Context.set_verify_depth` raises `TypeError` if called with a non-`int` argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_verify_depth(None) @@ -797,7 +858,7 @@ a chain to follow before giving up. The value can be retrieved with `Context.get_verify_depth`. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_verify_depth(11) assert context.get_verify_depth() == 11 @@ -807,10 +868,10 @@ passphrase. Return the path to the new file. """ key = PKey() - key.generate_key(TYPE_RSA, 512) + key.generate_key(TYPE_RSA, 1024) pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase) - with open(tmpfile, 'w') as fObj: - fObj.write(pem.decode('ascii')) + with open(tmpfile, "w") as fObj: + fObj.write(pem.decode("ascii")) return tmpfile def test_set_passwd_cb_wrong_args(self): @@ -818,7 +879,7 @@ `Context.set_passwd_cb` raises `TypeError` if called with a non-callable first argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_passwd_cb(None) @@ -834,7 +895,8 @@ def passphraseCallback(maxlen, verify, extra): calledWith.append((maxlen, verify, extra)) return passphrase - context = Context(TLSv1_METHOD) + + context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) context.use_privatekey_file(pemFile) assert len(calledWith) == 1 @@ -852,7 +914,7 @@ def passphraseCallback(maxlen, verify, extra): raise RuntimeError("Sorry, I am a fail.") - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(RuntimeError): context.use_privatekey_file(pemFile) @@ -867,7 +929,7 @@ def passphraseCallback(maxlen, verify, extra): return b"" - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) with pytest.raises(Error): context.use_privatekey_file(pemFile) @@ -882,7 +944,7 @@ def passphraseCallback(maxlen, verify, extra): return 10 - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) # TODO: Surely this is the wrong error? with pytest.raises(ValueError): @@ -901,7 +963,7 @@ assert maxlen == 1024 return passphrase + b"y" - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_passwd_cb(passphraseCallback) # This shall succeed because the truncated result is the correct # passphrase. @@ -914,19 +976,18 @@ """ (server, client) = socket_pair() - clientSSL = Connection(Context(TLSv1_METHOD), client) + clientSSL = Connection(Context(SSLv23_METHOD), client) clientSSL.set_connect_state() called = [] def info(conn, where, ret): called.append((conn, where, ret)) - context = Context(TLSv1_METHOD) + + context = Context(SSLv23_METHOD) context.set_info_callback(info) - context.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - context.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + context.use_certificate(load_certificate(FILETYPE_PEM, root_cert_pem)) + context.use_privatekey(load_privatekey(FILETYPE_PEM, root_key_pem)) serverSSL = Connection(context, server) serverSSL.set_accept_state() @@ -939,10 +1000,44 @@ # 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.") + conn + for (conn, where, ret) in called + if not isinstance(conn, Connection) + ] + assert ( + [] == notConnections + ), "Some info callback arguments were not Connection instances." + + @pytest.mark.skipif( + not getattr(_lib, "Cryptography_HAS_KEYLOG", None), + reason="SSL_CTX_set_keylog_callback unavailable", + ) + def test_set_keylog_callback(self): + """ + `Context.set_keylog_callback` accepts a callable which will be + invoked when key material is generated or received. + """ + called = [] + + def keylog(conn, line): + called.append((conn, line)) + + server_context = Context(TLSv1_2_METHOD) + server_context.set_keylog_callback(keylog) + server_context.use_certificate( + load_certificate(FILETYPE_PEM, root_cert_pem) + ) + server_context.use_privatekey( + load_privatekey(FILETYPE_PEM, root_key_pem) + ) + + client_context = Context(SSLv23_METHOD) + + self._handshake_test(server_context, client_context) + + assert called + assert all(isinstance(conn, Connection) for conn, line in called) + assert all(b"CLIENT_RANDOM" in line for conn, line in called) def _load_verify_locations_test(self, *args): """ @@ -952,22 +1047,25 @@ """ (server, client) = socket_pair() - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_METHOD) clientContext.load_verify_locations(*args) # Require that the server certificate verify properly or the # connection will fail. clientContext.set_verify( VERIFY_PEER, - lambda conn, cert, errno, depth, preverify_ok: preverify_ok) + lambda conn, cert, errno, depth, preverify_ok: preverify_ok, + ) clientSSL = Connection(clientContext, client) clientSSL.set_connect_state() - serverContext = Context(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + load_certificate(FILETYPE_PEM, root_cert_pem) + ) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + load_privatekey(FILETYPE_PEM, root_key_pem) + ) serverSSL = Connection(serverContext, server) serverSSL.set_accept_state() @@ -979,7 +1077,7 @@ 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): """ @@ -988,8 +1086,8 @@ 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(cleartextCertificatePEM.decode('ascii')) + with open(cafile, "w") as fObj: + fObj.write(root_cert_pem.decode("ascii")) self._load_verify_locations_test(cafile) @@ -1015,7 +1113,7 @@ `Context.load_verify_locations` raises `Error` when passed a non-existent cafile. """ - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_METHOD) with pytest.raises(Error): clientContext.load_verify_locations(tmpfile) @@ -1030,10 +1128,10 @@ # 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(cleartextCertificatePEM.decode('ascii')) + with open(cafile, "w") as fObj: + fObj.write(root_cert_pem.decode("ascii")) self._load_verify_locations_test(None, capath) @@ -1060,7 +1158,7 @@ `Context.load_verify_locations` raises `TypeError` if with non-`str` arguments. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.load_verify_locations(object()) with pytest.raises(TypeError): @@ -1069,7 +1167,7 @@ @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): """ @@ -1080,19 +1178,19 @@ SSL_CTX_SET_default_verify_paths so that it can't find certs unless it loads via fallback. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_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() @@ -1105,7 +1203,7 @@ """ Test that we return True/False appropriately if the env vars are set. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) dir_var = "CUSTOM_DIR_VAR" file_var = "CUSTOM_FILE_VAR" assert context._check_env_vars_set(dir_var, file_var) is False @@ -1118,13 +1216,13 @@ """ Test that we don't use the fallback path if env vars are set. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) monkeypatch.setattr( _lib, "SSL_CTX_set_default_verify_paths", lambda x: 1 ) - dir_env_var = _ffi.string( - _lib.X509_get_default_cert_dir_env() - ).decode("ascii") + 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") @@ -1133,16 +1231,14 @@ 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): """ @@ -1160,7 +1256,8 @@ 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.connect(("encrypted.google.com", 443)) @@ -1176,18 +1273,16 @@ Test that when passed empty arrays or paths that do not exist no errors are raised. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_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(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.add_extra_chain_cert(object()) @@ -1218,11 +1313,13 @@ The first argument passed to the verify callback is the `Connection` instance for which verification is taking place. """ - serverContext = Context(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + load_privatekey(FILETYPE_PEM, root_key_pem) + ) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + load_certificate(FILETYPE_PEM, root_cert_pem) + ) serverConnection = Connection(serverContext, None) class VerifyCallback(object): @@ -1231,7 +1328,7 @@ return 1 verify = VerifyCallback() - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_PEER, verify.callback) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() @@ -1247,18 +1344,20 @@ get_subject. This test sets up a handshake where we call get_subject on the cert provided to the verify callback. """ - serverContext = Context(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + load_privatekey(FILETYPE_PEM, root_key_pem) + ) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + load_certificate(FILETYPE_PEM, root_cert_pem) + ) serverConnection = Connection(serverContext, None) def verify_cb_get_subject(conn, cert, errnum, depth, ok): assert cert.get_subject() return 1 - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_PEER, verify_cb_get_subject) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() @@ -1273,14 +1372,17 @@ """ serverContext = Context(TLSv1_2_METHOD) serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) + load_privatekey(FILETYPE_PEM, root_key_pem) + ) serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) + load_certificate(FILETYPE_PEM, root_cert_pem) + ) clientContext = Context(TLSv1_2_METHOD) def verify_callback(*args): raise Exception("silly verify failure") + clientContext.set_verify(VERIFY_PEER, verify_callback) with pytest.raises(Exception) as exc: @@ -1288,6 +1390,74 @@ 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` @@ -1305,29 +1475,30 @@ # 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(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) # The client already has cacert, we only need to give them icert. serverContext.add_extra_chain_cert(icert) # Create the client - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_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. @@ -1351,22 +1522,23 @@ 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(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_certificate_chain_file(chainFile) serverContext.use_privatekey(skey) - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_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) @@ -1396,7 +1568,7 @@ `Context.use_certificate_chain_file` raises `TypeError` if passed a non-byte string single argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.use_certificate_chain_file(object()) @@ -1406,7 +1578,7 @@ passed a bad chain file name (for example, the name of a file which does not exist). """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(Error): context.use_certificate_chain_file(tmpfile) @@ -1415,29 +1587,28 @@ `Context.get_verify_mode` returns the verify mode flags previously passed to `Context.set_verify`. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) assert context.get_verify_mode() == 0 - context.set_verify( - VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) + context.set_verify(VERIFY_PEER | VERIFY_CLIENT_ONCE) assert context.get_verify_mode() == (VERIFY_PEER | VERIFY_CLIENT_ONCE) - @pytest.mark.parametrize('mode', [None, 1.0, object(), 'mode']) + @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(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): - context.set_verify(mode=mode, callback=lambda *args: None) + context.set_verify(mode=mode) - @pytest.mark.parametrize('callback', [None, 1.0, 'mode', ('foo', 'bar')]) + @pytest.mark.parametrize("callback", [1.0, "mode", ("foo", "bar")]) def test_set_verify_wrong_callable_arg(self, callback): """ `Context.set_verify` raises `TypeError` if the second argument is not callable. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_verify(mode=VERIFY_PEER, callback=callback) @@ -1446,7 +1617,7 @@ `Context.load_tmp_dh` raises `TypeError` if called with a non-`str` argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.load_tmp_dh(object()) @@ -1455,7 +1626,7 @@ `Context.load_tmp_dh` raises `OpenSSL.SSL.Error` if the specified file does not exist. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(Error): context.load_tmp_dh(b"hello") @@ -1464,12 +1635,11 @@ Verify that calling ``Context.load_tmp_dh`` with the given filename does not raise an exception. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_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): """ @@ -1494,7 +1664,7 @@ `Context.set_tmp_ecdh` sets the elliptic curve for Diffie-Hellman to the specified curve. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) for curve in get_elliptic_curves(): if curve.name.startswith(u"Oakley-"): # Setting Oakley-EC2N-4 and Oakley-EC2N-3 adds @@ -1511,7 +1681,7 @@ a non-integer argument. called with other than one integer argument. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) with pytest.raises(TypeError): context.set_session_cache_mode(object()) @@ -1520,7 +1690,7 @@ `Context.set_session_cache_mode` specifies how sessions are cached. The setting can be retrieved via `Context.get_session_cache_mode`. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_session_cache_mode(SESS_CACHE_OFF) off = context.set_session_cache_mode(SESS_CACHE_BOTH) assert SESS_CACHE_OFF == off @@ -1530,7 +1700,7 @@ """ `Context.get_cert_store` returns a `X509Store` instance. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) store = context.get_cert_store() assert isinstance(store, X509Store) @@ -1540,9 +1710,9 @@ It raises a TypeError if the list of profiles is not a byte string. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_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): """ @@ -1550,9 +1720,9 @@ It raises an Error if the call to OpenSSL fails. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_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): """ @@ -1560,8 +1730,8 @@ It does not return anything. """ - context = Context(TLSv1_METHOD) - assert context.set_tlsext_use_srtp(b'SRTP_AES128_CM_SHA1_80') is None + context = Context(SSLv23_METHOD) + assert context.set_tlsext_use_srtp(b"SRTP_AES128_CM_SHA1_80") is None class TestServerNameCallback(object): @@ -1569,18 +1739,20 @@ 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(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(callback) tracker = ref(callback) @@ -1611,7 +1783,8 @@ def servername(conn): args.append((conn, conn.get_servername())) - context = Context(TLSv1_METHOD) + + context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(servername) # Lose our reference to it. The Context is responsible for keeping it @@ -1622,13 +1795,14 @@ # 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(TLSv1_METHOD), None) + client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() interact_in_memory(server, client) @@ -1646,19 +1820,21 @@ def servername(conn): args.append((conn, conn.get_servername())) - context = Context(TLSv1_METHOD) + + context = Context(SSLv23_METHOD) context.set_tlsext_servername_callback(servername) # Necessary to actually accept the connection context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) + 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(TLSv1_METHOD), None) + client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() client.set_tlsext_host_name(b"foo1.example.com") @@ -1667,41 +1843,36 @@ assert args == [(server, b"foo1.example.com")] -@pytest.mark.skipif( - not _lib.Cryptography_HAS_NEXTPROTONEG, reason="NPN is not available" -) -class TestNextProtoNegotiation(object): +class TestApplicationLayerProtoNegotiation(object): """ - Test for Next Protocol Negotiation in PyOpenSSL. + Tests for ALPN in PyOpenSSL. """ - def test_npn_success(self): + + def test_alpn_success(self): """ - 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. + Clients and servers that agree on the negotiated ALPN protocol can + correct establish a connection, and 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" - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) + client_context = Context(SSLv23_METHOD) + client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) + 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) @@ -1712,79 +1883,76 @@ interact_in_memory(server, client) - assert advertise_args == [(server,)] - assert select_args == [(client, [b'http/1.1', b'spdy/2'])] + assert select_args == [(server, [b"http/1.1", b"spdy/2"])] - assert server.get_next_proto_negotiated() == b'spdy/2' - assert client.get_next_proto_negotiated() == b'spdy/2' + assert server.get_alpn_proto_negotiated() == b"spdy/2" + assert client.get_alpn_proto_negotiated() == b"spdy/2" - def test_npn_client_fail(self): + def test_alpn_set_on_connection(self): """ - Tests that when clients and servers cannot agree on what protocol - to use next that the TLS connection does not get established. + The same as test_alpn_success, but setting the ALPN protocols on + the connection rather than the context. """ - 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'' + return b"spdy/2" - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) + # Setup the client context but don't set any ALPN protocols. + client_context = Context(SSLv23_METHOD) - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) + 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() - # If the client doesn't return anything, the connection will fail. - with pytest.raises(Error): - interact_in_memory(server, client) + 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" - def test_npn_select_error(self): + def test_alpn_server_fail(self): """ - Test that we can handle exceptions in the select callback. If - select fails it should be fatal to the connection. + When clients and servers cannot agree on what protocol to use next + the TLS connection does not get established. """ - advertise_args = [] - - def advertise(conn): - advertise_args.append((conn,)) - return [b'http/1.1', b'spdy/2'] + select_args = [] def select(conn, options): - raise TypeError + select_args.append((conn, options)) + return b"" - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) + client_context = Context(SSLv23_METHOD) + client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) + 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) @@ -1793,39 +1961,37 @@ client = Connection(client_context, None) client.set_connect_state() - # If the callback throws an exception it should be raised here. - with pytest.raises(TypeError): + # If the client doesn't return anything, the connection will fail. + with pytest.raises(Error): interact_in_memory(server, client) - assert advertise_args == [(server,), ] - def test_npn_advertise_error(self): + assert select_args == [(server, [b"http/1.1", b"spdy/2"])] + + def test_alpn_no_server_overlap(self): """ - Test that we can handle exceptions in the advertise callback. If - advertise fails no NPN is advertised to the client. + A server can allow a TLS handshake to complete without + agreeing to an application protocol by returning + ``NO_OVERLAPPING_PROTOCOLS``. """ - select_args = [] - - def advertise(conn): - raise TypeError + refusal_args = [] - def select(conn, options): # pragma: nocover - """ - Assert later that no args are actually appended. - """ - select_args.append((conn, options)) - return b'' + def refusal(conn, options): + refusal_args.append((conn, options)) + return NO_OVERLAPPING_PROTOCOLS - server_context = Context(TLSv1_METHOD) - server_context.set_npn_advertise_callback(advertise) + client_context = Context(SSLv23_METHOD) + client_context.set_alpn_protos([b"http/1.1", b"spdy/2"]) - client_context = Context(TLSv1_METHOD) - client_context.set_npn_select_callback(select) + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(refusal) # Necessary to actually accept the connection server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) + 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) @@ -1834,293 +2000,125 @@ client = Connection(client_context, None) client.set_connect_state() - # If the client doesn't return anything, the connection will fail. - with pytest.raises(TypeError): - interact_in_memory(server, client) - assert select_args == [] - - -class TestApplicationLayerProtoNegotiation(object): - """ - Tests for ALPN in PyOpenSSL. - """ - # Skip tests on versions that don't support ALPN. - if _lib.Cryptography_HAS_ALPN: + # Do the dance. + interact_in_memory(server, client) - 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)) + assert refusal_args == [(server, [b"http/1.1", b"spdy/2"])] - # 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_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 = [] - interact_in_memory(server, client) + def invalid_cb(conn, options): + invalid_cb_args.append((conn, options)) + return u"can't return unicode" - 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"]) - assert server.get_alpn_proto_negotiated() == b'spdy/2' - assert client.get_alpn_proto_negotiated() == b'spdy/2' + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(invalid_cb) - def test_alpn_set_on_connection(self): - """ - The same as test_alpn_success, but setting the ALPN protocols on - the connection rather than the context. - """ - select_args = [] - - def select(conn, options): - select_args.append((conn, options)) - return b'spdy/2' - - # Setup the client context but don't set any ALPN protocols. - client_context = Context(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)) + # 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() - # 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() + client = Connection(client_context, None) + client.set_connect_state() + # Do the dance. + with pytest.raises(TypeError): interact_in_memory(server, client) - assert select_args == [(server, [b'http/1.1', b'spdy/2'])] - - assert server.get_alpn_proto_negotiated() == b'spdy/2' - assert client.get_alpn_proto_negotiated() == b'spdy/2' - - def test_alpn_server_fail(self): - """ - When clients and servers cannot agree on what protocol to use next - the TLS connection does not get established. - """ - select_args = [] - - def select(conn, options): - select_args.append((conn, options)) - return b'' - - client_context = Context(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() + assert invalid_cb_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 select_args == [(server, [b'http/1.1', b'spdy/2'])] - - def test_alpn_no_server_overlap(self): - """ - A server can allow a TLS handshake to complete without - agreeing to an application protocol by returning - ``NO_OVERLAPPING_PROTOCOLS``. - """ - refusal_args = [] - - def refusal(conn, options): - refusal_args.append((conn, options)) - return NO_OVERLAPPING_PROTOCOLS - - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(refusal) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) + assert client.get_alpn_proto_negotiated() == b"" - # Do a little connection to trigger the logic - server = Connection(server_context, None) - server.set_accept_state() + 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"]) - client = Connection(client_context, None) - client.set_connect_state() + server_context = Context(SSLv23_METHOD) - # Do the dance. - interact_in_memory(server, client) + # 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) + ) - assert refusal_args == [(server, [b'http/1.1', b'spdy/2'])] + # 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_select_cb_returns_invalid_value(self): - """ - If the ALPN selection callback returns anything other than - a bytestring or ``NO_OVERLAPPING_PROTOCOLS``, a - :py:exc:`TypeError` is raised. - """ - invalid_cb_args = [] - - def invalid_cb(conn, options): - invalid_cb_args.append((conn, options)) - return u"can't return unicode" - - client_context = Context(SSLv23_METHOD) - client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) - - server_context = Context(SSLv23_METHOD) - server_context.set_alpn_select_callback(invalid_cb) - - # Necessary to actually accept the connection - server_context.use_privatekey( - load_privatekey(FILETYPE_PEM, server_key_pem)) - server_context.use_certificate( - load_certificate(FILETYPE_PEM, server_cert_pem)) + # Do 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 = [] - # Do the dance. - with pytest.raises(TypeError): - interact_in_memory(server, client) + def select(conn, options): + select_args.append((conn, options)) + raise TypeError() - assert invalid_cb_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"]) - assert client.get_alpn_proto_negotiated() == b'' + server_context = Context(SSLv23_METHOD) + server_context.set_alpn_select_callback(select) - 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(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() - # Do the dance. + with pytest.raises(TypeError): interact_in_memory(server, client) - - assert client.get_alpn_proto_negotiated() == b'' - - def test_alpn_callback_exception(self): - """ - We can handle exceptions in the ALPN select callback. - """ - select_args = [] - - def select(conn, options): - select_args.append((conn, options)) - raise TypeError() - - client_context = Context(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) + assert select_args == [(server, [b"http/1.1", b"spdy/2"])] 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 @@ -2134,6 +2132,7 @@ """ Unit tests for `OpenSSL.SSL.Connection`. """ + # XXX get_peer_certificate -> None # XXX sock_shutdown # XXX master_key -> TypeError @@ -2152,10 +2151,10 @@ """ `Connection` can be used to create instances of that type. """ - ctx = Context(TLSv1_METHOD) - assert is_consistent_type(Connection, 'Connection', ctx, None) + ctx = Context(SSLv23_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` @@ -2164,13 +2163,13 @@ with pytest.raises(TypeError): Connection(bad_context) - @pytest.mark.parametrize('bad_bio', [object(), None, 1, [1, 2, 3]]) + @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(TLSv1_METHOD) + context = Context(SSLv23_METHOD) connection = Connection(context, None) with pytest.raises(TypeError): connection.bio_write(bad_bio) @@ -2180,19 +2179,19 @@ `Connection.bio_write` does not raise if called with bytes or bytearray, warns if called with text. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) connection = Connection(context, None) - connection.bio_write(b'xy') - connection.bio_write(bytearray(b'za')) + connection.bio_write(b"xy") + connection.bio_write(bytearray(b"za")) with pytest.warns(DeprecationWarning): - connection.bio_write(u'deprecated') + 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(TLSv1_METHOD) + context = Context(SSLv23_METHOD) connection = Connection(context, None) assert connection.get_context() is context @@ -2201,7 +2200,7 @@ `Connection.set_context` raises `TypeError` if called with a non-`Context` instance argument. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_context(object()) @@ -2217,7 +2216,7 @@ used for the connection. """ original = Context(SSLv23_METHOD) - replacement = Context(TLSv1_METHOD) + replacement = Context(SSLv23_METHOD) connection = Connection(original, None) connection.set_context(replacement) assert replacement is connection.get_context() @@ -2232,7 +2231,7 @@ 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(TLSv1_METHOD), None) + conn = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): conn.set_tlsext_host_name(object()) with pytest.raises(TypeError): @@ -2248,7 +2247,7 @@ `Connection.pending` returns the number of bytes available for immediate read. """ - connection = Connection(Context(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) assert connection.pending() == 0 def test_peek(self): @@ -2257,17 +2256,17 @@ 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(TLSv1_METHOD), socket_any_family()) + connection = Connection(Context(SSLv23_METHOD), socket_any_family()) with pytest.raises(TypeError): connection.connect(None) @@ -2277,7 +2276,7 @@ connect method raises it. """ client = socket_any_family() - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) clientSSL = Connection(context, client) # pytest.raises here doesn't work because of a bug in py.test on Python # 2.6: https://github.com/pytest-dev/pytest/issues/988 @@ -2292,16 +2291,16 @@ `Connection.connect` establishes a connection to the specified address. """ port = socket_any_family() - port.bind(('', 0)) + port.bind(("", 0)) port.listen(3) - clientSSL = Connection(Context(TLSv1_METHOD), socket(port.family)) + clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) clientSSL.connect((loopback_address(port), 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): """ @@ -2309,10 +2308,10 @@ errno instead of raising an exception. """ port = socket_any_family() - port.bind(('', 0)) + port.bind(("", 0)) port.listen(3) - clientSSL = Connection(Context(TLSv1_METHOD), socket(port.family)) + clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) clientSSL.setblocking(False) result = clientSSL.connect_ex(port.getsockname()) expected = (EINPROGRESS, EWOULDBLOCK) @@ -2324,15 +2323,15 @@ tuple of a new `Connection` (the accepted client) and the address the connection originated from. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) port = socket_any_family() portSSL = Connection(ctx, port) - portSSL.bind(('', 0)) + portSSL.bind(("", 0)) portSSL.listen(3) - clientSSL = Connection(Context(TLSv1_METHOD), socket(port.family)) + clientSSL = Connection(Context(SSLv23_METHOD), socket(port.family)) # Calling portSSL.getsockname() here to get the server IP address # sounds great, but frequently fails on Windows. @@ -2349,7 +2348,7 @@ `Connection.set_shutdown` raises `TypeError` if called with arguments other than integers. """ - connection = Connection(Context(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.set_shutdown(None) @@ -2388,12 +2387,14 @@ If the underlying connection is truncated, `Connection.shutdown` raises an `Error`. """ - server_ctx = Context(TLSv1_METHOD) - client_ctx = Context(TLSv1_METHOD) + server_ctx = Context(SSLv23_METHOD) + client_ctx = Context(SSLv23_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) @@ -2409,7 +2410,7 @@ `Connection.set_shutdown` sets the state of the SSL connection shutdown process. """ - connection = Connection(Context(TLSv1_METHOD), socket_any_family()) + connection = Connection(Context(SSLv23_METHOD), socket_any_family()) connection.set_shutdown(RECEIVED_SHUTDOWN) assert connection.get_shutdown() == RECEIVED_SHUTDOWN @@ -2423,10 +2424,12 @@ 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): @@ -2435,7 +2438,7 @@ `Connection.set_app_data` and later retrieved with `Connection.get_app_data`. """ - conn = Connection(Context(TLSv1_METHOD), None) + conn = Connection(Context(SSLv23_METHOD), None) assert None is conn.get_app_data() app_data = object() conn.set_app_data(app_data) @@ -2446,7 +2449,7 @@ `Connection.makefile` is not implemented and calling that method raises `NotImplementedError`. """ - conn = Connection(Context(TLSv1_METHOD), None) + conn = Connection(Context(SSLv23_METHOD), None) with pytest.raises(NotImplementedError): conn.makefile() @@ -2457,7 +2460,7 @@ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) context.use_certificate(scert) client = Connection(context, None) cert = client.get_certificate() @@ -2470,7 +2473,7 @@ If there is no certificate, it returns None. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) client = Connection(context, None) cert = client.get_certificate() assert cert is None @@ -2483,7 +2486,7 @@ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain - serverContext = Context(TLSv1_METHOD) + serverContext = Context(SSLv23_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) serverContext.add_extra_chain_cert(icert) @@ -2492,7 +2495,7 @@ server.set_accept_state() # Create the client - clientContext = Context(TLSv1_METHOD) + clientContext = Context(SSLv23_METHOD) clientContext.set_verify(VERIFY_NONE, verify_cb) client = Connection(clientContext, None) client.set_connect_state() @@ -2510,22 +2513,79 @@ `Connection.get_peer_cert_chain` returns `None` if the peer sends no certificate chain. """ - ctx = Context(TLSv1_METHOD) + 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(TLSv1_METHOD), None) + client = Connection(Context(SSLv23_METHOD), None) client.set_connect_state() interact_in_memory(client, server) assert None is server.get_peer_cert_chain() + def test_get_verified_chain(self): + """ + `Connection.get_verified_chain` returns a list of certificates + which the connected server returned for the certification verification. + """ + chain = _create_certificate_chain() + [(cakey, cacert), (ikey, icert), (skey, scert)] = chain + + serverContext = Context(SSLv23_METHOD) + serverContext.use_privatekey(skey) + serverContext.use_certificate(scert) + serverContext.add_extra_chain_cert(icert) + serverContext.add_extra_chain_cert(cacert) + server = Connection(serverContext, None) + server.set_accept_state() + + # Create the client + clientContext = Context(SSLv23_METHOD) + # cacert is self-signed so the client must trust it for verification + # to succeed. + clientContext.get_cert_store().add_cert(cacert) + clientContext.set_verify(VERIFY_PEER, verify_cb) + client = Connection(clientContext, None) + client.set_connect_state() + + interact_in_memory(client, server) + + chain = client.get_verified_chain() + assert len(chain) == 3 + assert "Server Certificate" == chain[0].get_subject().CN + assert "Intermediate Certificate" == chain[1].get_subject().CN + assert "Authority Certificate" == chain[2].get_subject().CN + + def test_get_verified_chain_none(self): + """ + `Connection.get_verified_chain` returns `None` if the peer sends + no certificate chain. + """ + ctx = Context(SSLv23_METHOD) + ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) + ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) + server = Connection(ctx, None) + server.set_accept_state() + client = Connection(Context(SSLv23_METHOD), None) + client.set_connect_state() + interact_in_memory(client, server) + assert None is server.get_verified_chain() + + def test_get_verified_chain_unconnected(self): + """ + `Connection.get_verified_chain` returns `None` when used with an object + which has not been connected. + """ + ctx = Context(SSLv23_METHOD) + server = Connection(ctx, None) + assert None is server.get_verified_chain() + def test_get_session_unconnected(self): """ `Connection.get_session` returns `None` when used with an object which has not been connected. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) server = Connection(ctx, None) session = server.get_session() assert None is session @@ -2554,7 +2614,7 @@ `Connection.set_session` raises `TypeError` if called with an object that is not an instance of `Session`. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) with pytest.raises(TypeError): connection.set_session(123) @@ -2581,17 +2641,17 @@ 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 @@ -2607,24 +2667,15 @@ with a context using a different SSL method than the `Connection` is using, a `OpenSSL.SSL.Error` is raised. """ - # 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") + v1 = TLSv1_2_METHOD + v2 = TLSv1_METHOD key = load_privatekey(FILETYPE_PEM, server_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) ctx = Context(v1) ctx.use_privatekey(key) ctx.use_certificate(cert) - ctx.set_session_id("unity-test") + ctx.set_session_id(b"unity-test") def makeServer(socket): server = Connection(ctx, socket) @@ -2637,7 +2688,8 @@ return client originalServer, originalClient = loopback( - server_factory=makeServer, client_factory=makeOriginalClient) + server_factory=makeServer, client_factory=makeOriginalClient + ) originalSession = originalClient.get_session() def makeClient(socket): @@ -2673,9 +2725,10 @@ 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(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, client_socket) # Client's speak first, so make it an SSL client conn.set_connect_state() @@ -2689,7 +2742,7 @@ `Connection.get_finished` returns `None` before TLS handshake is completed. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) assert connection.get_finished() is None @@ -2698,7 +2751,7 @@ `Connection.get_peer_finished` returns `None` before TLS handshake is completed. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) connection = Connection(ctx, None) assert connection.get_peer_finished() is None @@ -2742,7 +2795,7 @@ `Connection.get_cipher_name` returns `None` if no connection has been established. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_name() is None @@ -2752,8 +2805,10 @@ 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) @@ -2765,7 +2820,7 @@ `Connection.get_cipher_version` returns `None` if no connection has been established. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_version() is None @@ -2775,8 +2830,10 @@ 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) @@ -2788,7 +2845,7 @@ `Connection.get_cipher_bits` returns `None` if no connection has been established. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) assert conn.get_cipher_bits() is None @@ -2798,8 +2855,10 @@ 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) @@ -2839,18 +2898,18 @@ `Connection.bio_read` raises `OpenSSL.SSL.WantReadError` if there are no bytes available to be read from the BIO. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) with pytest.raises(WantReadError): conn.bio_read(1024) - @pytest.mark.parametrize('bufsize', [1.0, None, object(), 'bufsize']) + @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(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) with pytest.raises(TypeError): conn.bio_read(bufsize) @@ -2860,7 +2919,7 @@ `Connection.bio_read` accepts an integer giving the maximum number of bytes to read and return. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) conn = Connection(ctx, None) conn.set_connect_state() try: @@ -2875,12 +2934,13 @@ """ 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(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) ciphers = connection.get_cipher_list() assert isinstance(ciphers, list) for cipher in ciphers: @@ -2891,20 +2951,22 @@ """ 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(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.send(object()) with pytest.raises(TypeError): @@ -2916,9 +2978,9 @@ 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): """ @@ -2929,12 +2991,11 @@ 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): """ @@ -2943,9 +3004,9 @@ of bytes sent. """ server, client = loopback() - count = server.send(memoryview(b'xy')) + count = server.send(memoryview(b"xy")) assert count == 2 - assert client.recv(2) == b'xy' + assert client.recv(2) == b"xy" def test_short_bytearray(self): """ @@ -2953,9 +3014,9 @@ it and returns the number of bytes sent. """ server, client = loopback() - count = server.send(bytearray(b'xy')) + count = server.send(bytearray(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): @@ -2965,13 +3026,13 @@ of bytes sent. """ server, client = loopback() - count = server.send(buffer(b'xy')) + count = server.send(buffer(b"xy")) # noqa: F821 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): """ @@ -2979,7 +3040,7 @@ `Connection.send` bails out as SSL_write only accepts an int for the buffer length. """ - connection = Connection(Context(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(ValueError) as exc_info: connection.send(VeryLarge()) exc_info.match(r"Cannot send more than .+ bytes at once") @@ -2997,6 +3058,7 @@ """ Tests for `Connection.recv_into`. """ + def _no_length_test(self, factory): """ Assert that when the given buffer is passed to `Connection.recv_into`, @@ -3006,10 +3068,10 @@ 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): """ @@ -3027,10 +3089,10 @@ 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): """ @@ -3049,12 +3111,12 @@ 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): """ @@ -3075,12 +3137,12 @@ 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): """ @@ -3119,12 +3181,13 @@ """ 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(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) with pytest.raises(TypeError): connection.sendall(object()) with pytest.raises(TypeError): @@ -3136,8 +3199,8 @@ 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): """ @@ -3148,10 +3211,9 @@ 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): @@ -3160,8 +3222,8 @@ `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): @@ -3170,9 +3232,9 @@ `Connection.sendall` transmits all of them. """ server, client = loopback() - count = server.sendall(buffer(b'xy')) + count = server.sendall(buffer(b"xy")) # noqa: F821 assert count == 2 - assert client.recv(2) == b'xy' + assert client.recv(2) == b"xy" def test_long(self): """ @@ -3183,7 +3245,7 @@ # 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 @@ -3191,7 +3253,7 @@ data = client.recv(1024) accum.append(data) received += len(data) - assert message == b''.join(accum) + assert message == b"".join(accum) def test_closed(self): """ @@ -3212,12 +3274,13 @@ """ Tests for SSL renegotiation APIs. """ + def test_total_renegotiations(self): """ `Connection.total_renegotiations` returns `0` before any renegotiations have happened. """ - connection = Connection(Context(TLSv1_METHOD), None) + connection = Connection(Context(SSLv23_METHOD), None) assert connection.total_renegotiations() == 0 def test_renegotiate(self): @@ -3255,12 +3318,13 @@ """ 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): @@ -3271,9 +3335,10 @@ 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): """ @@ -3285,7 +3350,7 @@ @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): """ @@ -3296,7 +3361,7 @@ @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): """ @@ -3307,7 +3372,9 @@ @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): """ @@ -3381,23 +3448,26 @@ """ 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(TLSv1_METHOD) + server_ctx = Context(SSLv23_METHOD) server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE) server_ctx.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, - verify_cb + 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 @@ -3412,17 +3482,19 @@ """ # Now create the client side Connection. Similar boilerplate to the # above. - client_ctx = Context(TLSv1_METHOD) + client_ctx = Context(SSLv23_METHOD) client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE) client_ctx.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT | VERIFY_CLIENT_ONCE, - verify_cb + 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) @@ -3459,39 +3531,41 @@ 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): """ @@ -3521,13 +3595,13 @@ Test that `OpenSSL.SSL.bio_read` and `OpenSSL.SSL.bio_write` don't work on `OpenSSL.SSL.Connection`() that use sockets. """ - context = Context(TLSv1_METHOD) + context = Context(SSLv23_METHOD) client = socket_any_family() clientSSL = Connection(context, client) with pytest.raises(TypeError): clientSSL.bio_read(100) with pytest.raises(TypeError): - clientSSL.bio_write("foo") + clientSSL.bio_write(b"foo") with pytest.raises(TypeError): clientSSL.bio_shutdown() @@ -3611,7 +3685,7 @@ `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(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.set_client_ca_list("spam") with pytest.raises(TypeError): @@ -3624,9 +3698,11 @@ 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): @@ -3643,6 +3719,7 @@ 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): @@ -3663,6 +3740,7 @@ L = [sedesc, cldesc] ctx.set_client_ca_list(L) return L + self._check_client_ca_list(multiple_ca) def test_reset_ca_list(self): @@ -3683,6 +3761,7 @@ 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): @@ -3702,6 +3781,7 @@ 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): @@ -3709,7 +3789,7 @@ `Context.add_client_ca` raises `TypeError` if called with a non-X509 object. """ - ctx = Context(TLSv1_METHOD) + ctx = Context(SSLv23_METHOD) with pytest.raises(TypeError): ctx.add_client_ca("spam") @@ -3724,6 +3804,7 @@ 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): @@ -3741,6 +3822,7 @@ 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): @@ -3761,6 +3843,7 @@ 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): @@ -3781,6 +3864,7 @@ ctx.set_client_ca_list([cadesc]) ctx.add_client_ca(secert) return [cadesc, sedesc] + self._check_client_ca_list(set_replaces_add_ca) @@ -3788,6 +3872,7 @@ """ Tests for assorted constants exposed for use in info callbacks. """ + def test_integers(self): """ All of the info constants are integers. @@ -3797,17 +3882,31 @@ 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) @@ -3817,6 +3916,7 @@ 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 @@ -3854,6 +3954,7 @@ """ 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): @@ -3898,6 +3999,7 @@ 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") @@ -3923,7 +4025,7 @@ handshake_in_memory(client, server) assert len(called) == 1 - assert called[0] == b'' + assert called[0] == b"" def test_client_receives_servers_data(self): """ @@ -4006,7 +4108,7 @@ client_calls = [] def server_callback(*args): - return b'' + return b"" def client_callback(conn, ocsp_data, ignored): client_calls.append(ocsp_data) @@ -4017,12 +4119,13 @@ 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 @@ -4039,6 +4142,7 @@ """ The callbacks thrown in the client callback bubble up to the caller. """ + class SentinelException(Exception): pass @@ -4058,6 +4162,7 @@ """ The callbacks thrown in the server callback bubble up to the caller. """ + class SentinelException(Exception): pass @@ -4077,8 +4182,9 @@ """ 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 -Nru pyopenssl-19.1.0/tests/test_tsafe.py pyopenssl-20.0.1/tests/test_tsafe.py --- pyopenssl-19.1.0/tests/test_tsafe.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/test_tsafe.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -# 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 -Nru pyopenssl-19.1.0/tests/test_util.py pyopenssl-20.0.1/tests/test_util.py --- pyopenssl-19.1.0/tests/test_util.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/test_util.py 2020-12-15 15:30:54.000000000 +0000 @@ -7,6 +7,7 @@ """ 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 -Nru pyopenssl-19.1.0/tests/util.py pyopenssl-20.0.1/tests/util.py --- pyopenssl-19.1.0/tests/util.py 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tests/util.py 2020-12-15 15:30:54.000000000 +0000 @@ -59,7 +59,7 @@ 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 @@ """ a = self.anInstance() b = self.anInstance() - assert (a == b) + assert a == b def test_sameNe(self): """ @@ -102,7 +102,7 @@ """ a = self.anInstance() b = self.anotherInstance() - assert (a != b) + assert a != b def test_anotherTypeEq(self): """ @@ -120,13 +120,14 @@ """ 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. @@ -141,6 +142,7 @@ 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. diff -Nru pyopenssl-19.1.0/tox.ini pyopenssl-20.0.1/tox.ini --- pyopenssl-19.1.0/tox.ini 2019-11-18 04:58:38.000000000 +0000 +++ pyopenssl-20.0.1/tox.ini 2020-12-15 15:30:54.000000000 +0000 @@ -1,5 +1,5 @@ [tox] -envlist = {pypy,pypy3,py27,py34,py35,py36,py37,py38}{,-cryptographyMaster,-cryptographyMinimum}{,-randomorder},py27-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report +envlist = {pypy,pypy3,py27,py35,py36,py37,py38,py39}{,-cryptographyMaster,-cryptographyMinimum}{,-randomorder},py37-twistedMaster,pypi-readme,check-manifest,flake8,docs,coverage-report [testenv] whitelist_externals = @@ -10,7 +10,7 @@ deps = coverage>=4.2 cryptographyMaster: git+https://github.com/pyca/cryptography.git - cryptographyMinimum: cryptography==2.8 + cryptographyMinimum: cryptography==3.2 randomorder: pytest-randomly setenv = # Do not allow the executing environment to pollute the test environment @@ -22,40 +22,25 @@ coverage run --parallel -m OpenSSL.debug coverage run --parallel -m pytest -v {posargs} -[testenv:py27-twistedMaster] +[testenv:py37-twistedMaster] deps = - # [tls,conch] syntax doesn't work here so we enumerate all dependencies. - git+https://github.com/twisted/twisted - idna - service_identity - bcrypt + Twisted[all_non_platform] @ git+https://github.com/twisted/twisted +setenv = 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 = - flake8 + black + flake8 skip_install = true commands = - flake8 src tests examples setup.py + black --check . + flake8 . [testenv:pypi-readme] deps = @@ -84,3 +69,6 @@ commands = coverage combine coverage report + +[flake8] +ignore = E203,W503,W504