pem-19.1.0/0000755000076500000240000000000013444265115012631 5ustar hynekstaff00000000000000pem-19.1.0/.coveragerc0000644000076500000240000000024412654641726014762 0ustar hynekstaff00000000000000[run] branch = True source = pem [paths] source = src/pem .tox/*/lib/python*/site-packages/pem .tox/pypy*/site-packages/pem [report] show_missing = True pem-19.1.0/.pre-commit-config.yaml0000644000076500000240000000132513444240740017110 0ustar hynekstaff00000000000000repos: - repo: https://github.com/ambv/black rev: 19.3b0 hooks: - id: black language_version: python3.7 # override until resolved: https://github.com/ambv/black/issues/402 files: \.pyi?$ types: [] - repo: https://github.com/asottile/seed-isort-config rev: v1.7.0 hooks: - id: seed-isort-config - repo: https://github.com/pre-commit/mirrors-isort rev: v4.3.15 hooks: - id: isort language_version: python3.7 - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.1.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - id: flake8 language_version: python3.7 pem-19.1.0/.readthedocs.yml0000644000076500000240000000017313444254041015714 0ustar hynekstaff00000000000000--- version: 2 python: version: 3.7 install: - method: pip path: . extra_requirements: - docs pem-19.1.0/.travis.yml0000644000076500000240000000301313444261773014745 0ustar hynekstaff00000000000000dist: xenial group: travis_latest cache: directories: - $HOME/.cache/pip language: python matrix: include: # Lint - python: "3.7" stage: lint env: TOXENV=lint - python: "3.7" env: TOXENV=mypy - python: "3.7" env: TOXENV=manifest # Test - python: "2.7" stage: test env: TOXENV=py27-twisted - python: "2.7" env: TOXENV=py27 - python: "3.4" env: TOXENV=py34-twisted - python: "3.4" env: TOXENV=py34 - python: "3.5" env: TOXENV=py35-twisted - python: "3.5" env: TOXENV=py35 - python: "3.6" env: TOXENV=py36-twisted - python: "3.6" env: TOXENV=py36 - python: "3.7" env: TOXENV=py37 - python: "3.7" env: TOXENV=py37-twisted - python: "pypy" env: TOXENV=pypy-twisted dist: trusty - python: "pypy" env: TOXENV=pypy dist: trusty - python: "pypy3" env: TOXENV=pypy3 dist: trusty - python: "pypy3" env: TOXENV=pypy3-twisted dist: trusty # Prevent breakage by a new releases - python: "3.7-dev" env: TOXENV=py37 - python: "3.7-dev" env: TOXENV=py37-twisted # Docs - python: "3.7" stage: docs env: TOXENV=docs - python: "3.7" env: TOXENV=pypi-description allow_failures: - python: "3.7-dev" install: - pip install tox script: - tox before_install: - pip install codecov after_success: - tox -e coverage-report - codecov notifications: email: false pem-19.1.0/AUTHORS.rst0000644000076500000240000000042612654671265014523 0ustar hynekstaff00000000000000Credits ======= ``pem`` is written and maintained by Hynek Schlawack. The development is kindly supported by `Variomedia AG `_. A full list of contributors can be found on GitHub’s `overview `_. pem-19.1.0/CHANGELOG.rst0000644000076500000240000001104513444264276014662 0ustar hynekstaff00000000000000.. :changelog: Changelog ========= Versions are year-based with a strict backward compatibility policy. The third digit is only for regressions. 19.1.0 (2019-03-19) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - You can now load encrypted PKCS#8 PEM key as ``pem.Key``. - Added support for ``pem.PublicKey`` (``PUBLIC KEY``). - Added support for ``pem.RSAPublicKey`` (``RSA PUBLIC KEY``). ---- 18.2.0 (2018-10-09) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - Added ``pem.CertificateRevocationList`` for certificate revocation lists (CRLs). `#32 `_ ---- 18.1.0 (2018-06-23) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ``pem.certificateOptionsFromFiles()`` and ``pem.certificateOptionsFromPEMs()`` have been removed after three years of deprecation. Please use ``pem.twisted.certificateOptionsFromFiles()`` ``pem.twisted.certificateOptionsFromPEMs()`` instead. - Diffie-Hellman support for Twisted older than 14.0 has been removed. Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - ``pem`` now ships with typing information that can be used by type checkers like `mypy `_. - PEM objects now have an ``obj.sha1_hexdigest`` property with the SHA-1 digest of the stored bytes as a native string. This is the same digest as the one that is used by the PEM objects' ``__repr__``\ s. - PEM objects now have an ``obj.as_text()`` method that returns the PEM-encoded content as unicode, always. ---- 17.1.0 (2017-08-10) ------------------- Changes: ^^^^^^^^ - Added ``pem.CertificateRequest`` for `certificate signing requests `_. `#29 `_ ---- 16.1.0 (2016-04-08) ------------------- Deprecations: ^^^^^^^^^^^^^ - Passing ``dhParameters`` to ``pem.twisted.certifateOptionsFromPEMs`` and ``certificateOptionsFromFiles`` is now deprecated; instead, include the DH parameters in the PEM objects or files. Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Python 3.3 and 2.6 aren't supported anymore. They may work by chance but any effort to keep them working has ceased. The last Python 2.6 release was on October 29, 2013 and isn't supported by the CPython core team anymore. Major Python packages like Django and Twisted dropped Python 2.6 a while ago already. Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release. Changes: ^^^^^^^^ - ``pem.twisted.certificateOptionsFromPEMs`` and ``certificateOptionsFromFiles`` will now load Ephemeral Diffie-Hellman parameters if found. `#21 `_ - PEM objects now correctly handle being constructed with unicode and bytes on both Python 2 and 3. `#24 `_ - PEM objects now have an ``as_bytes`` method that returns the PEM-encoded content as bytes, always. `#24 `_ - PEM objects are now hashable and comparable for equality. `#25 `_ ---- 16.0.0 (2016-02-05) ------------------- Changes: ^^^^^^^^ - PKCS #8 keys are now supported. `#14 `_ - ``pem`` is now fully functional without installing Twisted. `#16 `_ ---- 15.0.0 (2015-07-10) ------------------- Deprecations: ^^^^^^^^^^^^^ - The usage of Twisted helpers from the pem module is deprecated. Use their pendants from the ``pem.twisted`` module now. - The usage of the backport of ephemeral Diffie-Hellman support is hereby deprecated. Nobody should use a Twisted release that is older than 14.0.0 because it contains essential SSL/TLS fixes. Changes: ^^^^^^^^ - Support PEM strings that do not end with a new line. `#12 `_ - Support PEM strings that end with ``\r\n``. - The Twisted-related helpers have been moved to ``pem.twisted``. ---- 0.3.0 (2014-04-15) ------------------ Changes: ^^^^^^^^ - Load PEM files as UTF-8 to allow for non-ASCII comments (like in certifi). - Allow keys, primary certificates, and chain certificates to occur in any order. ---- 0.2.0 (2014-03-13) ------------------ Changes: ^^^^^^^^ - Add forward-compatible support for DHE. ---- 0.1.0 (2013-07-18) ------------------ Initial release. pem-19.1.0/CODE_OF_CONDUCT.rst0000644000076500000240000000626213311141106015630 0ustar hynekstaff00000000000000Contributor Covenant Code of Conduct ==================================== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hs@ox.cx. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant `_, version 1.4, available at . pem-19.1.0/CONTRIBUTING.rst0000644000076500000240000001535413444261463015304 0ustar hynekstaff00000000000000How To Contribute ================= First off, thank you for considering contributing to ``pem``! It's people like *you* who make it is such a great tool for everyone. This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone. But don't be afraid to open half-finished PRs and ask questions if something is unclear! Workflow -------- - No contribution is too small! Please submit as many fixes for typos and grammar bloopers as you can! - Try to limit each pull request to *one* change only. - Since we squash on merge, it's up to you how you handle updates to the master branch. Whether you prefer to rebase on master or merge master into your branch, do whatever is more comfortable for you. - *Always* add tests and docs for your code. This is a hard rule; patches with missing tests or documentation can't be merged. - Consider updating CHANGELOG.rst to reflect the changes as observed by people using this library. - Make sure your changes pass our CI_. You won't get any feedback until it's green unless you ask for it. - Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. - Don’t break `backward compatibility`_. Code ---- - Obey `PEP 8`_ and `PEP 257`_. We use the ``"""``\ -on-separate-lines style for docstrings: .. code-block:: python def func(x): """ Do something. :param str x: A very important parameter. :rtype: str """ - If you add or change public APIs, tag the docstring using ``.. versionadded:: 16.0.0 WHAT`` or ``.. versionchanged:: 17.1.0 WHAT``. - We use isort_ to sort our imports, and we follow the Black_ code style with a line length of 79 characters. As long as you run our full tox suite before committing, or install our pre-commit_ hooks (ideally you'll do both -- see below "Local Development Environment"), you won't have to spend any time on formatting your code at all. If you don't, CI will catch it for you -- but that seems like a waste of your time! Tests ----- - Write your asserts as ``expected == actual`` to line them up nicely: .. code-block:: python x = f() assert 42 == x.some_attribute assert "foo" == x._a_private_attribute - To run the test suite, all you need is a recent tox_. It will ensure the test suite runs with all dependencies against all Python versions just as it will on Travis CI. If you lack some Python versions, you can can always limit the environments like ``tox -e py27,py35`` (in that case you may want to look into pyenv_, which makes it very easy to install many different Python versions in parallel). - Write `good test docstrings`_. Documentation ------------- - Use `semantic newlines`_ in reStructuredText_ files (files ending in ``.rst``): .. code-block:: rst This is a sentence. This is another sentence. - If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other: .. code-block:: rst Last line of previous section. Header of New Top Section ------------------------- Header of New Section ^^^^^^^^^^^^^^^^^^^^^ First line of new section. - If your change is noteworthy, add an entry to the changelog_. Use `semantic newlines`_, and add a link to your pull request: .. code-block:: rst - Added ``pem.func()`` that does foo. It's pretty cool. [`#1 `_] - ``pem.func()`` now doesn't crash the Large Hadron Collider anymore. That was a nasty bug! [`#2 `_] Local Development Environment ----------------------------- You can (and should) run our test suite using tox_. However, you’ll probably want a more traditional environment as well. We highly recommend to develop using the latest Python 3 release because you're more likely to catch certain bugs earlier. First create a `virtual environment `_. It’s out of scope for this document to list all the ways to manage virtual environments in Python, but if you don’t already have a pet way, take some time to look at tools like `pew `_, `virtualfish `_, and `virtualenvwrapper `_. Next get an up to date checkout of the ``pem`` repository: .. code-block:: bash git clone git@github.com:hynek/pem.git Change into the newly created directory and **after activating your virtual environment** install an editable version of ``pem`` along with its tests and docs requirements: .. code-block:: bash cd pem pip install -e .[dev] At this point, .. code-block:: bash $ python -m pytest should work and pass, as should: .. code-block:: bash $ cd docs $ make html The built documentation can then be found in ``docs/_build/html/``. To avoid committing code that violates our style guide, we strongly advice you to install pre-commit_ [#f1]_ hooks: .. code-block:: bash $ pre-commit install You can also run them anytime (as our tox does) using: .. code-block:: bash $ pre-commit run --all-files .. [#f1] pre-commit should have been installed into your virtualenv automatically when you ran ``pip install -e .[dev]`` above. If pre-commit is missing, it may be that you need to re-run ``pip install -e .[dev]``. **** Again, this list is mainly to help you to get started by codifying tribal knowledge and expectations. If something is unclear, feel free to ask for help! Please note that this project is released with a Contributor `Code of Conduct`_. By participating in this project you agree to abide by its terms. Please report any harm to `Hynek Schlawack`_ in any way you find appropriate. Thank you for considering contributing to ``pem``! .. _`Hynek Schlawack`: https://hynek.me/about/ .. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/ .. _`PEP 257`: https://www.python.org/dev/peps/pep-0257/ .. _`good test docstrings`: https://jml.io/pages/test-docstrings.html .. _`Code of Conduct`: https://github.com/hynek/pem/blob/master/CODE_OF_CONDUCT.rst .. _changelog: https://github.com/hynek/pem/blob/master/CHANGELOG.rst .. _`backward compatibility`: https://pem.readthedocs.io/en/latest/backward-compatibility.html .. _tox: https://tox.readthedocs.io/ .. _pyenv: https://github.com/pyenv/pyenv .. _reStructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html .. _semantic newlines: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ .. _CI: https://travis-ci.org/hynek/pem/ .. _black: https://github.com/ambv/black .. _pre-commit: https://pre-commit.com/ .. _isort: https://github.com/timothycrosley/isort pem-19.1.0/LICENSE0000644000076500000240000000205012455220311013621 0ustar hynekstaff00000000000000Copyright (c) 2012-2015 Hynek Schlawack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pem-19.1.0/MANIFEST.in0000644000076500000240000000046713444237427014403 0ustar hynekstaff00000000000000include *.rst LICENSE tox.ini dev-requirements.txt docs-requirements.txt .coveragerc pyproject.toml conftest.py *.yml *.yaml mypy.ini recursive-include src *.typed recursive-include docs *.bat recursive-include docs *.py recursive-include docs *.rst recursive-include docs Makefile recursive-include tests *.py pem-19.1.0/PKG-INFO0000644000076500000240000001253513444265115013734 0ustar hynekstaff00000000000000Metadata-Version: 2.1 Name: pem Version: 19.1.0 Summary: Easy PEM file parsing in Python. Home-page: https://pem.readthedocs.io/ Author: Hynek Schlawack Author-email: hs@ox.cx Maintainer: Hynek Schlawack Maintainer-email: hs@ox.cx License: MIT Project-URL: Documentation, https://pem.readthedocs.io/ Project-URL: Bug Tracker, https://github.com/hynek/pem/issues Project-URL: Source Code, https://github.com/hynek/pem Description: pem: Easy PEM file parsing ========================== .. image:: https://readthedocs.org/projects/pem/badge/?version=stable :target: https://pem.readthedocs.io/en/stable/?badge=stable :alt: Documentation Status .. image:: https://travis-ci.org/hynek/pem.svg?branch=master :target: https://travis-ci.org/hynek/pem :alt: CI status .. image:: https://codecov.io/gh/hynek/pem/branch/master/graph/badge.svg :target: https://codecov.io/github/hynek/pem :alt: Coverage .. image:: https://www.irccloud.com/invite-svg?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 :target: https://www.irccloud.com/invite?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black :alt: Code style: black .. image:: http://www.mypy-lang.org/static/mypy_badge.svg :target: http://mypy-lang.org :alt: Checked with mypy .. teaser-begin ``pem`` is an MIT_-licensed Python module for parsing and splitting of `PEM files`_, i.e. Base64 encoded DER keys and certificates. It runs on Python 2.7, 3.4+, and PyPy, has no dependencies, and does not attempt to interpret the certificate data in any way. It’s born from the need to load keys, certificates, trust chains, and DH parameters from various certificate deployments: some servers (like Apache_) expect them to be a separate file, others (like nginx_) expect them concatenated to the server certificate and finally some (like HAProxy_) expect key, certificate, and chain to be in one file. With ``pem``, your Python application can cope with all of those scenarios: .. code-block:: pycon >>> import pem >>> certs = pem.parse_file("chain.pem") >>> certs [, ] >>> str(certs[0]) '-----BEGIN CERTIFICATE-----\n...' Additionally to the vanilla parsing code, ``pem`` also contains helpers for Twisted_ that save a lot of boilerplate code. ``pem``\ ’s documentation lives at `Read the Docs `_, the code on `GitHub `_. .. _MIT: https://choosealicense.com/licenses/mit/ .. _`PEM files`: https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions .. _Apache: https://httpd.apache.org/ .. _nginx: https://nginx.org/ .. _HAProxy: https://www.haproxy.org/ .. _Twisted: https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.Certificate.html#loadPEM Release Information =================== 19.1.0 (2019-03-19) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - You can now load encrypted PKCS#8 PEM key as ``pem.Key``. - Added support for ``pem.PublicKey`` (``PUBLIC KEY``). - Added support for ``pem.RSAPublicKey`` (``RSA PUBLIC KEY``). `Full changelog `_. Credits ======= ``pem`` is written and maintained by Hynek Schlawack. The development is kindly supported by `Variomedia AG `_. A full list of contributors can be found on GitHub’s `overview `_. Keywords: pyopenssl,ssl,tls,pem,cryptography,twisted Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy 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: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: dev Provides-Extra: docs Provides-Extra: tests pem-19.1.0/README.rst0000644000076500000240000000501513313410011014277 0ustar hynekstaff00000000000000pem: Easy PEM file parsing ========================== .. image:: https://readthedocs.org/projects/pem/badge/?version=stable :target: https://pem.readthedocs.io/en/stable/?badge=stable :alt: Documentation Status .. image:: https://travis-ci.org/hynek/pem.svg?branch=master :target: https://travis-ci.org/hynek/pem :alt: CI status .. image:: https://codecov.io/gh/hynek/pem/branch/master/graph/badge.svg :target: https://codecov.io/github/hynek/pem :alt: Coverage .. image:: https://www.irccloud.com/invite-svg?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 :target: https://www.irccloud.com/invite?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black :alt: Code style: black .. image:: http://www.mypy-lang.org/static/mypy_badge.svg :target: http://mypy-lang.org :alt: Checked with mypy .. teaser-begin ``pem`` is an MIT_-licensed Python module for parsing and splitting of `PEM files`_, i.e. Base64 encoded DER keys and certificates. It runs on Python 2.7, 3.4+, and PyPy, has no dependencies, and does not attempt to interpret the certificate data in any way. It’s born from the need to load keys, certificates, trust chains, and DH parameters from various certificate deployments: some servers (like Apache_) expect them to be a separate file, others (like nginx_) expect them concatenated to the server certificate and finally some (like HAProxy_) expect key, certificate, and chain to be in one file. With ``pem``, your Python application can cope with all of those scenarios: .. code-block:: pycon >>> import pem >>> certs = pem.parse_file("chain.pem") >>> certs [, ] >>> str(certs[0]) '-----BEGIN CERTIFICATE-----\n...' Additionally to the vanilla parsing code, ``pem`` also contains helpers for Twisted_ that save a lot of boilerplate code. ``pem``\ ’s documentation lives at `Read the Docs `_, the code on `GitHub `_. .. _MIT: https://choosealicense.com/licenses/mit/ .. _`PEM files`: https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions .. _Apache: https://httpd.apache.org/ .. _nginx: https://nginx.org/ .. _HAProxy: https://www.haproxy.org/ .. _Twisted: https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.Certificate.html#loadPEM pem-19.1.0/codecov.yml0000644000076500000240000000025713311650213014770 0ustar hynekstaff00000000000000--- comment: false coverage: status: patch: default: target: "100" project: default: target: "100" pem-19.1.0/conftest.py0000644000076500000240000000021113311142012015001 0ustar hynekstaff00000000000000collect_ignore = [] try: import twisted.internet.ssl # noqa except ImportError: collect_ignore.append("tests/test_twisted.py") pem-19.1.0/docs/0000755000076500000240000000000013444265115013561 5ustar hynekstaff00000000000000pem-19.1.0/docs/Makefile0000644000076500000240000001634512547453400015231 0ustar hynekstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pem.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pem.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pem" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pem" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." pem-19.1.0/docs/api.rst0000644000076500000240000000172113444257077015075 0ustar hynekstaff00000000000000.. _api: API Reference ============= Core ---- .. currentmodule:: pem See :doc:`core` for examples. Parsers ^^^^^^^ .. autofunction:: parse .. autofunction:: parse_file .. _pem-objects: PEM Objects ^^^^^^^^^^^ The following objects can be returned by the parsing functions. .. autoclass:: Certificate(AbstractPEMObject) .. autoclass:: Key(AbstractPEMObject) .. autoclass:: PrivateKey(Key) .. autoclass:: PublicKey(Key) .. autoclass:: RSAPrivateKey(PrivateKey) .. autoclass:: RSAPublicKey(PublicKey) .. autoclass:: DHParameters(AbstractPEMObject) .. autoclass:: CertificateRequest(AbstractPEMObject) .. autoclass:: CertificateRevocationList(AbstractPEMObject) Their shared provided API is minimal: .. autoclass:: AbstractPEMObject :members: __str__, as_bytes, as_text, sha1_hexdigest Twisted ------- .. currentmodule:: pem.twisted See :doc:`twisted` for examples. .. autofunction:: certificateOptionsFromFiles .. autofunction:: certificateOptionsFromPEMs pem-19.1.0/docs/backward-compatibility.rst0000644000076500000240000000104112547713355020743 0ustar hynekstaff00000000000000Backward Compatibility ====================== This project has a very strong backward compatibility policy that is inspired by the one of the `Twisted framework `_. Put simply, you shouldn't ever be afraid to upgrade if you're using its public APIs. If there is the need to break compatibility: 1. It will be announced in the :doc:`changelog`, 2. a deprecation warning will be raised for a year, 3. backward compatibility will be finally broken no earlier than one year after step 1. pem-19.1.0/docs/changelog.rst0000644000076500000240000000003612654656557016261 0ustar hynekstaff00000000000000.. include:: ../CHANGELOG.rst pem-19.1.0/docs/conf.py0000644000076500000240000002432013313404231015046 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- # # pem documentation build configuration file, created by # sphinx-quickstart on Thu Jul 9 13:12:00 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import codecs import datetime import os import re try: import sphinx_rtd_theme except ImportError: sphinx_rtd_theme = None def read(*parts): """ Build an absolute path from *parts* and and return the contents of the resulting file. Assume UTF-8 encoding. """ here = os.path.abspath(os.path.dirname(__file__)) with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: return f.read() def find_version(*file_paths): """ Build a path from *file_paths* and search for a ``__version__`` string inside. """ version_file = read(*file_paths) version_match = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M ) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = "index" # General information about the project. project = u"pem" author = u"Hynek Schlawack" year = datetime.date.today().year copyright = u"2013{0}, {1}".format(u"-{0}".format(year), author) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = find_version("../src/pem/__init__.py") # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- html_theme = "alabaster" html_theme_options = { "font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif', "head_font_family": '"Avenir Next", Calibri, "PT Sans", sans-serif', "font_size": "18px", "page_width": "980px", "show_relbars": True, } # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = "pemdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', # Latex figure (float) alignment # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, "pem.tex", u"pem Documentation", author, "manual") ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "pem", u"pem Documentation", [author], 1)] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "pem", u"pem Documentation", author, "pem", "One line description of project.", "Miscellaneous", ) ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # Refer to the Python standard library. intersphinx_mapping = {"https://docs.python.org/3/": None} pem-19.1.0/docs/contributing.rst0000644000076500000240000000006412547473757017042 0ustar hynekstaff00000000000000.. _contributing: .. include:: ../CONTRIBUTING.rst pem-19.1.0/docs/core.rst0000644000076500000240000000160613444262367015254 0ustar hynekstaff00000000000000Core API ======== The core API call are the function :func:`pem.parse` and the its convenience helper :func:`pem.parse_file`:: import pem with open("cert.pem", "rb") as f: certs = pem.parse(f.read()) # or: certs = pem.parse_file("cert.pem") The function returns a list of valid :ref:`PEM objects ` found in the string supplied. - They can be transformed using ``str(obj)`` into native strings, - or using ``obj.as_text()`` into Unicode text (``str`` on Python 3, ``unicode`` on Python 2), - or using ``obj.as_bytes()`` into bytes. - Additional you can obtain the SHA-1 hexdigest using ``obj.hashdigest()`` for quick comparison of objects. Files ^^^^^ For convenience, there's the helper function :func:`pem.parse_file` that reads a file and parses its contents. So the following example is equivalent with the first one:: certs = pem.parse_file("cert.pem") pem-19.1.0/docs/index.rst0000644000076500000240000000102012547744026015421 0ustar hynekstaff00000000000000===================================== pem: Easy PEM file parsing in Python. ===================================== Release v\ |release| (:doc:`What's new? `). .. include:: ../README.rst :start-after: teaser-begin User's Guide ------------ .. toctree:: :maxdepth: 1 core twisted api Project Information ------------------- .. toctree:: :maxdepth: 1 license contributing backward-compatibility changelog Indices and tables ================== * :ref:`genindex` * :ref:`search` pem-19.1.0/docs/license.rst0000644000076500000240000000047713143056101015732 0ustar hynekstaff00000000000000License and Hall of Fame ======================== ``pem`` is licensed under the permissive `MIT `_ license. The full license text can be also found in the `source code repository `_. .. _authors: .. include:: ../AUTHORS.rst pem-19.1.0/docs/make.bat0000644000076500000240000001610612547453400015171 0ustar hynekstaff00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 2> nul if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pem.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pem.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end pem-19.1.0/docs/twisted.rst0000644000076500000240000000375613311234023015774 0ustar hynekstaff00000000000000Twisted ======= A typical use case in Twisted with the core API would be:: import pem from twisted.internet import ssl key = pem.parse_file("key.pem") cert, chain = pem.parse_file("cert_and_chain.pem") cert = ssl.PrivateCertificate.loadPEM(str(key) + str(cert)) chainCert = ssl.Certificate.loadPEM(str(chain)) dhParams = ssl.DiffieHellmanParameters(str(pem.parse_file("dhparams.pem"))) ctxFactory = ssl.CertificateOptions( privateKey=cert.privateKey.original, certificate=cert.original, extraCertChain=[chainCert.original], dhParameters=dhParams, ) Turns out, this is a major use case. Therefore it can be simplified to:: ctxFactory = pem.twisted.certificateOptionsFromFiles( "key.pem", "cert_and_chain.pem", "dhparams.pem", ) There must be exactly one private key present. The certificate matching the private key will be used as the server certificate, the rest is passed as the chain. There must be no more than one set of DH parameters. You can pass as many PEM files as you like. Therefore you can distribute your key, certificate, chain certificates, and DH parameters over a arbitrary number of files. A ``ValueError`` is raised if more than one key, no key, or no certificate are found. Any further keyword arguments will be passed to CertificateOptions_. Passing ``dhParameters`` directly as a keyword argument is deprecated; pass these as part of the PEM files instead. If you want to load your PEM data from somewhere else, you can also use :func:`pem.twisted.certificateOptionsFromPEMs` to do the same thing with already-loaded PEM objects, like so:: myPems = [] pems = pem.parse("""\ -----BEGIN RSA PRIVATE KEY----- ... -----END RSA PRIVATE KEY----- -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- """) ctxFactory = pem.twisted.certificateOptionsFromPEMs(pems) .. _CertificateOptions: https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.CertificateOptions.html pem-19.1.0/mypy.ini0000644000076500000240000000053613313410011014312 0ustar hynekstaff00000000000000[mypy] # show error messages from unrelated files follow_imports=normal # suppress errors about unsatisfied imports ignore_missing_imports=True # be strict disallow_untyped_calls=True warn_return_any=True strict_optional=True warn_no_return=True warn_redundant_casts=True warn_unused_ignores=True disallow_untyped_defs=True check_untyped_defs=True pem-19.1.0/pyproject.toml0000644000076500000240000000020213430224357015535 0ustar hynekstaff00000000000000[build-system] requires = ["setuptools>=40.6.0", "wheel"] build-backend = "setuptools.build_meta" [tool.black] line-length = 79 pem-19.1.0/setup.cfg0000644000076500000240000000066113444265115014455 0ustar hynekstaff00000000000000[tool:pytest] minversion = 3.0 strict = true addopts = -ra testpaths = tests [bdist_wheel] universal = 1 [metadata] license_file = LICENSE [isort] atomic = true include_trailing_comma = true lines_after_imports = 2 lines_between_types = 1 multi_line_output = 3 not_skip = __init__.py known_first_party = pem known_third_party = OpenSSL,certifi,cryptography,pretend,pytest,setuptools,twisted [egg_info] tag_build = tag_date = 0 pem-19.1.0/setup.py0000644000076500000240000000672613420046611014346 0ustar hynekstaff00000000000000import codecs import os import re from setuptools import find_packages, setup ############################################################################### NAME = "pem" KEYWORDS = ["pyopenssl", "ssl", "tls", "pem", "cryptography", "twisted"] PROJECT_URLS = { "Documentation": "https://pem.readthedocs.io/", "Bug Tracker": "https://github.com/hynek/pem/issues", "Source Code": "https://github.com/hynek/pem", } CLASSIFIERS = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "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", "Topic :: Software Development :: Libraries :: Python Modules", ] INSTALL_REQUIRES = [] EXTRAS_REQUIRE = { "docs": ["sphinx"], "tests": ["coverage", "pytest", "certifi", "pretend", "pyopenssl"], } EXTRAS_REQUIRE["dev"] = ( EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["docs"] + ["twisted[tls]", "pre-commit"] ) PACKAGE_DATA = {"pem": ["py.typed"]} ############################################################################### HERE = os.path.abspath(os.path.dirname(__file__)) def read(*parts): """ Build an absolute path from *parts* and and return the contents of the resulting file. Assume UTF-8 encoding. """ with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f: return f.read() try: PACKAGES except NameError: PACKAGES = find_packages(where="src") try: META_PATH except NameError: META_PATH = os.path.join(HERE, "src", NAME, "__init__.py") finally: META_FILE = read(META_PATH) def find_meta(meta): """ Extract __*meta*__ from META_FILE. """ meta_match = re.search( r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M ) if meta_match: return meta_match.group(1) raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta)) URL = find_meta("url") LONG = ( read("README.rst") + "\n\n" + "Release Information\n" + "===================\n\n" + re.search( r"(\d+.\d.\d \(.*?\)\n.*?)\n\n\n----\n\n\n", read("CHANGELOG.rst"), re.S, ).group(1) + "\n\n`Full changelog " + "<{url}en/stable/changelog.html>`_.\n\n".format(url=URL) + read("AUTHORS.rst") ) if __name__ == "__main__": setup( name=NAME, description=find_meta("description"), license=find_meta("license"), url=URL, project_urls=PROJECT_URLS, version=find_meta("version"), author=find_meta("author"), author_email=find_meta("email"), maintainer=find_meta("author"), maintainer_email=find_meta("email"), long_description=LONG, keywords=KEYWORDS, packages=PACKAGES, package_dir={"": "src"}, package_data=PACKAGE_DATA, include_package_data=True, zip_safe=False, classifiers=CLASSIFIERS, install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, ) pem-19.1.0/src/0000755000076500000240000000000013444265115013420 5ustar hynekstaff00000000000000pem-19.1.0/src/pem/0000755000076500000240000000000013444265115014201 5ustar hynekstaff00000000000000pem-19.1.0/src/pem/__init__.py0000644000076500000240000000155413444264276016326 0ustar hynekstaff00000000000000from __future__ import absolute_import, division, print_function from ._core import ( AbstractPEMObject, Certificate, CertificateRequest, CertificateRevocationList, DHParameters, Key, PrivateKey, PublicKey, RSAPrivateKey, RSAPublicKey, parse, parse_file, ) try: from . import twisted except ImportError: twisted = None # type: ignore __version__ = "19.1.0" __author__ = "Hynek Schlawack" __license__ = "MIT" __description__ = "Easy PEM file parsing in Python." __url__ = "https://pem.readthedocs.io/" __uri__ = __url__ __email__ = "hs@ox.cx" __all__ = [ "AbstractPEMObject", "Certificate", "CertificateRequest", "CertificateRevocationList", "DHParameters", "Key", "PrivateKey", "PublicKey", "RSAPrivateKey", "RSAPublicKey", "parse", "parse_file", "twisted", ] pem-19.1.0/src/pem/_compat.py0000644000076500000240000000050013313410011016146 0ustar hynekstaff00000000000000from __future__ import absolute_import, division, print_function import sys __all__ = ["ABC", "text_type", "PY2"] PY2 = sys.version_info[0] == 2 if PY2: import abc text_type = unicode # noqa class ABC(object): __metaclass__ = abc.ABCMeta else: from abc import ABC text_type = str pem-19.1.0/src/pem/_core.py0000644000076500000240000001114513444256752015653 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- """ Framework agnostic PEM file parsing functions. """ from __future__ import absolute_import, division, print_function import hashlib import re from ._compat import ABC, PY2, text_type # mypy hack: Import typing information without actually importing anything. MYPY = False if MYPY: # pragma: nocover from typing import List, Any, Union, AnyStr, Optional, Dict, Type # noqa class AbstractPEMObject(ABC): """ Base class for parsed objects. """ def __init__(self, pem_bytes): # type: (Union[text_type, bytes]) -> None if isinstance(pem_bytes, text_type): self._pem_bytes = pem_bytes.encode("ascii") # type: bytes else: self._pem_bytes = pem_bytes # type: bytes self._sha1_hexdigest = None # type: Optional[str] def __str__(self): # type: () -> str """ Return the PEM-encoded content as a native :obj:`str`. """ if not PY2: return self._pem_bytes.decode("ascii") return self._pem_bytes def __repr__(self): # type: () -> str return "<{0}(PEM string with SHA-1 digest {1!r})>".format( self.__class__.__name__, self.sha1_hexdigest ) @property def sha1_hexdigest(self): # type: () -> str """ A SHA-1 digest of the whole object for easy differentiation. .. versionadded:: 18.1.0 """ if self._sha1_hexdigest is None: self._sha1_hexdigest = hashlib.sha1(self._pem_bytes).hexdigest() return self._sha1_hexdigest def as_bytes(self): # type: () -> bytes """ Return the PEM-encoded content as :obj:`bytes`. .. versionadded:: 16.1.0 """ return self._pem_bytes def as_text(self): # type: () -> text_type """ Return the PEM-encoded content as Unicode text. .. versionadded:: 18.1.0 """ return self._pem_bytes.decode("utf-8") def __eq__(self, other): # type: (object) -> Union[NotImplemented, bool] if not isinstance(other, type(self)): return NotImplemented return ( type(self) == type(other) and self._pem_bytes == other._pem_bytes ) def __ne__(self, other): # type: (object) -> Union[NotImplemented, bool] if not isinstance(other, type(self)): return NotImplemented return type(self) != type(other) or self._pem_bytes != other._pem_bytes def __hash__(self): # type: () -> int return hash(self._pem_bytes) class Certificate(AbstractPEMObject): """ A certificate. """ class CertificateRequest(AbstractPEMObject): """ A certificate signing request. .. versionadded:: 17.1.0 """ class CertificateRevocationList(AbstractPEMObject): """ A certificate revocation list. .. versionadded:: 18.2.0 """ class Key(AbstractPEMObject): """ A key of unknown type. """ class PrivateKey(Key): """ A private key of unknown type. .. versionadded:: 19.1.0 """ class PublicKey(Key): """ A public key of unknown type. .. versionadded:: 19.1.0 """ class RSAPrivateKey(PrivateKey): """ A private RSA key. """ class RSAPublicKey(PublicKey): """ A public RSA key. .. versionadded:: 19.1.0 """ class DHParameters(AbstractPEMObject): """ Diffie-Hellman parameters for DHE. """ _PEM_TO_CLASS = { b"CERTIFICATE": Certificate, b"PRIVATE KEY": PrivateKey, b"PUBLIC KEY": PublicKey, b"ENCRYPTED PRIVATE KEY": PrivateKey, b"RSA PRIVATE KEY": RSAPrivateKey, b"RSA PUBLIC KEY": RSAPublicKey, b"DH PARAMETERS": DHParameters, b"NEW CERTIFICATE REQUEST": CertificateRequest, b"CERTIFICATE REQUEST": CertificateRequest, b"X509 CRL": CertificateRevocationList, } # type: Dict[bytes, Type[AbstractPEMObject]] _PEM_RE = re.compile( b"-----BEGIN (" + b"|".join(_PEM_TO_CLASS.keys()) + b""")-----\r? .+?\r? -----END \\1-----\r?\n?""", re.DOTALL, ) def parse(pem_str): # type: (bytes) -> List[AbstractPEMObject] """ Extract PEM objects from *pem_str*. :param pem_str: String to parse. :type pem_str: bytes :return: list of :ref:`pem-objects` """ return [ _PEM_TO_CLASS[match.group(1)](match.group(0)) for match in _PEM_RE.finditer(pem_str) ] def parse_file(file_name): # type: (str) -> List[AbstractPEMObject] """ Read *file_name* and parse PEM objects from it using :func:`parse`. """ with open(file_name, "rb") as f: return parse(f.read()) pem-19.1.0/src/pem/py.typed0000644000076500000240000000000013313410011015644 0ustar hynekstaff00000000000000pem-19.1.0/src/pem/twisted.py0000644000076500000240000000671213313410011016222 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- """ Twisted-specific convenience helpers. """ from __future__ import absolute_import, division, print_function from OpenSSL.SSL import FILETYPE_PEM from twisted.internet import ssl from ._core import Certificate, DHParameters, Key, parse_file # mypy hack: Import typing information without actually importing anything. MYPY = False if MYPY: # pragma: nocover from ._core import AbstractPEMObject # noqa from typing import List, Any # noqa def certificateOptionsFromPEMs(pemObjects, **kw): # type: (List[AbstractPEMObject], **Any) -> ssl.CerticateOptions """ Load a CertificateOptions from the given collection of PEM objects (already-loaded private keys and certificates). In those PEM objects, identify one private key and its corresponding certificate to use as the primary certificate. Then use the rest of the certificates found as chain certificates. Raise a ValueError if no certificate matching a private key is found. :return: A TLS context factory using *pemObjects* :rtype: `twisted.internet.ssl.CertificateOptions`_ .. _`twisted.internet.ssl.CertificateOptions`: \ https://twistedmatrix.com/documents/current/api/\ twisted.internet.ssl.CertificateOptions.html """ keys = [key for key in pemObjects if isinstance(key, Key)] if not len(keys): raise ValueError("Supplied PEM file(s) does *not* contain a key.") if len(keys) > 1: raise ValueError("Supplied PEM file(s) contains *more* than one key.") privateKey = ssl.KeyPair.load(str(keys[0]), FILETYPE_PEM) certs = [cert for cert in pemObjects if isinstance(cert, Certificate)] if not len(certs): raise ValueError("*At least one* certificate is required.") certificates = [ssl.Certificate.loadPEM(str(certPEM)) for certPEM in certs] certificatesByFingerprint = dict( [ (certificate.getPublicKey().keyHash(), certificate) for certificate in certificates ] ) if privateKey.keyHash() not in certificatesByFingerprint: raise ValueError( "No certificate matching {fingerprint} found.".format( fingerprint=privateKey.keyHash() ) ) primaryCertificate = certificatesByFingerprint.pop(privateKey.keyHash()) if "dhParameters" in kw: raise TypeError( "Passing DH parameters as a keyword argument instead of a " "PEM object is not supported anymore." ) dhparams = [o for o in pemObjects if isinstance(o, DHParameters)] if len(dhparams) > 1: raise ValueError( "Supplied PEM file(s) contain(s) *more* than one set of DH " "parameters." ) elif len(dhparams) == 1: kw["dhParameters"] = ssl.DiffieHellmanParameters(str(dhparams[0])) ctxFactory = ssl.CertificateOptions( privateKey=privateKey.original, certificate=primaryCertificate.original, extraCertChain=[ chain.original for chain in certificatesByFingerprint.values() ], **kw ) return ctxFactory def certificateOptionsFromFiles(*pemFiles, **kw): # type: (*str, **Any) -> ssl.CertificateOptions """ Read all files named by *pemFiles*, and parse them using :func:`certificateOptionsFromPEMs`. """ pems = [] # type: List[AbstractPEMObject] for pemFile in pemFiles: pems += parse_file(pemFile) return certificateOptionsFromPEMs(pems, **kw) pem-19.1.0/src/pem.egg-info/0000755000076500000240000000000013444265115015673 5ustar hynekstaff00000000000000pem-19.1.0/src/pem.egg-info/PKG-INFO0000644000076500000240000001253513444265115016776 0ustar hynekstaff00000000000000Metadata-Version: 2.1 Name: pem Version: 19.1.0 Summary: Easy PEM file parsing in Python. Home-page: https://pem.readthedocs.io/ Author: Hynek Schlawack Author-email: hs@ox.cx Maintainer: Hynek Schlawack Maintainer-email: hs@ox.cx License: MIT Project-URL: Documentation, https://pem.readthedocs.io/ Project-URL: Bug Tracker, https://github.com/hynek/pem/issues Project-URL: Source Code, https://github.com/hynek/pem Description: pem: Easy PEM file parsing ========================== .. image:: https://readthedocs.org/projects/pem/badge/?version=stable :target: https://pem.readthedocs.io/en/stable/?badge=stable :alt: Documentation Status .. image:: https://travis-ci.org/hynek/pem.svg?branch=master :target: https://travis-ci.org/hynek/pem :alt: CI status .. image:: https://codecov.io/gh/hynek/pem/branch/master/graph/badge.svg :target: https://codecov.io/github/hynek/pem :alt: Coverage .. image:: https://www.irccloud.com/invite-svg?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 :target: https://www.irccloud.com/invite?channel=%23cryptography-dev&hostname=irc.freenode.net&port=6697&ssl=1 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black :alt: Code style: black .. image:: http://www.mypy-lang.org/static/mypy_badge.svg :target: http://mypy-lang.org :alt: Checked with mypy .. teaser-begin ``pem`` is an MIT_-licensed Python module for parsing and splitting of `PEM files`_, i.e. Base64 encoded DER keys and certificates. It runs on Python 2.7, 3.4+, and PyPy, has no dependencies, and does not attempt to interpret the certificate data in any way. It’s born from the need to load keys, certificates, trust chains, and DH parameters from various certificate deployments: some servers (like Apache_) expect them to be a separate file, others (like nginx_) expect them concatenated to the server certificate and finally some (like HAProxy_) expect key, certificate, and chain to be in one file. With ``pem``, your Python application can cope with all of those scenarios: .. code-block:: pycon >>> import pem >>> certs = pem.parse_file("chain.pem") >>> certs [, ] >>> str(certs[0]) '-----BEGIN CERTIFICATE-----\n...' Additionally to the vanilla parsing code, ``pem`` also contains helpers for Twisted_ that save a lot of boilerplate code. ``pem``\ ’s documentation lives at `Read the Docs `_, the code on `GitHub `_. .. _MIT: https://choosealicense.com/licenses/mit/ .. _`PEM files`: https://en.wikipedia.org/wiki/X.509#Certificate_filename_extensions .. _Apache: https://httpd.apache.org/ .. _nginx: https://nginx.org/ .. _HAProxy: https://www.haproxy.org/ .. _Twisted: https://twistedmatrix.com/documents/current/api/twisted.internet.ssl.Certificate.html#loadPEM Release Information =================== 19.1.0 (2019-03-19) ------------------- Backward-incompatible changes: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *none* Deprecations: ^^^^^^^^^^^^^ *none* Changes: ^^^^^^^^ - You can now load encrypted PKCS#8 PEM key as ``pem.Key``. - Added support for ``pem.PublicKey`` (``PUBLIC KEY``). - Added support for ``pem.RSAPublicKey`` (``RSA PUBLIC KEY``). `Full changelog `_. Credits ======= ``pem`` is written and maintained by Hynek Schlawack. The development is kindly supported by `Variomedia AG `_. A full list of contributors can be found on GitHub’s `overview `_. Keywords: pyopenssl,ssl,tls,pem,cryptography,twisted Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy 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: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: dev Provides-Extra: docs Provides-Extra: tests pem-19.1.0/src/pem.egg-info/SOURCES.txt0000644000076500000240000000140413444265115017556 0ustar hynekstaff00000000000000.coveragerc .pre-commit-config.yaml .readthedocs.yml .travis.yml AUTHORS.rst CHANGELOG.rst CODE_OF_CONDUCT.rst CONTRIBUTING.rst LICENSE MANIFEST.in README.rst codecov.yml conftest.py mypy.ini pyproject.toml setup.cfg setup.py tox.ini docs/Makefile docs/api.rst docs/backward-compatibility.rst docs/changelog.rst docs/conf.py docs/contributing.rst docs/core.rst docs/index.rst docs/license.rst docs/make.bat docs/twisted.rst src/pem/__init__.py src/pem/_compat.py src/pem/_core.py src/pem/py.typed src/pem/twisted.py src/pem.egg-info/PKG-INFO src/pem.egg-info/SOURCES.txt src/pem.egg-info/dependency_links.txt src/pem.egg-info/not-zip-safe src/pem.egg-info/requires.txt src/pem.egg-info/top_level.txt tests/__init__.py tests/data.py tests/test_core.py tests/test_twisted.pypem-19.1.0/src/pem.egg-info/dependency_links.txt0000644000076500000240000000000113444265115021741 0ustar hynekstaff00000000000000 pem-19.1.0/src/pem.egg-info/not-zip-safe0000644000076500000240000000000112654656744020137 0ustar hynekstaff00000000000000 pem-19.1.0/src/pem.egg-info/requires.txt0000644000076500000240000000022213444265115020267 0ustar hynekstaff00000000000000 [dev] coverage pytest certifi pretend pyopenssl sphinx twisted[tls] pre-commit [docs] sphinx [tests] coverage pytest certifi pretend pyopenssl pem-19.1.0/src/pem.egg-info/top_level.txt0000644000076500000240000000000413444265115020417 0ustar hynekstaff00000000000000pem pem-19.1.0/tests/0000755000076500000240000000000013444265115013773 5ustar hynekstaff00000000000000pem-19.1.0/tests/__init__.py0000644000076500000240000000000012547506557016105 0ustar hynekstaff00000000000000pem-19.1.0/tests/data.py0000644000076500000240000002474713444256117015276 0ustar hynekstaff00000000000000CERT_PEMS = [ b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK94OSlzVBsWMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA vtIM2QADJDHcqxZugx7MULbenrNUFrmoMDfEaedYveWY3wBxOw642L4nFWxN/fwL AgMBAAGjdzB1MB0GA1UdDgQWBBQ4O0ZSUfTA6C+Y+QZ3MpeMhysxYjBGBgNVHSME PzA9gBQ4O0ZSUfTA6C+Y+QZ3MpeMhysxYqEapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCveDkpc1QbFjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA XwKIF+Kf4OhcqbdQp253HG2KBt/WZwvNLo/bBlkrGYwfacbGuWT8nKJG70ujdKKf -----END CERTIFICATE----- """, b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK9X9aUr9pYtMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA v401YT8GeCt6oG076W/n7hxUsFO7sd74/4+2+4OcwMiLEp8BSRdWTk3g/tdF1YHT AgMBAAGjdzB1MB0GA1UdDgQWBBT/dStoZFKGlnfedA7gtJV1K8JYKDBGBgNVHSME PzA9gBT/dStoZFKGlnfedA7gtJV1K8JYKKEapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCvV/WlK/aWLTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA uuXLfr1DgOMNt9JGNY5mBjabj3P7ALQYglygEe+QB7d0b/mFngn/aG35TuF5aud9 -----END CERTIFICATE----- """, b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK4oWdJCuqj2MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA wfq/eNemUKJ287E0ydVkzBxX44E6WhmnAN3oq7M881CxXLseNXHe/CRqYGpmziN5 AgMBAAGjdzB1MB0GA1UdDgQWBBQNtv8Fx7AEj4VCmX1I08mk4/viVzBGBgNVHSME PzA9gBQNtv8Fx7AEj4VCmX1I08mk4/viV6EapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCuKFnSQrqo9jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA i3qou3qkVXNKuiAFe9dBvz0nhcpAZpXrpwc9R4Qk+rirEqkdCZI1feQKBz4J3ikm -----END CERTIFICATE----- """, ] KEY_PEM = b"""-----BEGIN RSA PRIVATE KEY----- MIHyAgEAAjEAvtIM2QADJDHcqxZugx7MULbenrNUFrmoMDfEaedYveWY3wBxOw64 2L4nFWxN/fwLAgMBAAECMCwqsCCV+SQqilnrQj8FJONVwGdZOJBd/iHi6ZXI2zbD Q9Rv3iOsmqoCb5mqiDra0QIZAPbJRoliNA+2w7/dfttmWcQzcq8xL8qnEwIZAMXx 3hQNtUjuvgohXhZeBkyjP+7G0tceKQIZAPD9sFHsgiZuNU2hgIXDtxkvnGiUQbVF 3QIYMSmKQ6bH8K5DCtcQvDNsExq0pURCV2VJAhgnscmQDJ+DZblOG4zzn4pK7POX OzCeivo= -----END RSA PRIVATE KEY----- """ KEY_PEM2 = b"""-----BEGIN RSA PRIVATE KEY----- MIH0AgEAAjEAv401YT8GeCt6oG076W/n7hxUsFO7sd74/4+2+4OcwMiLEp8BSRdW Tk3g/tdF1YHTAgMBAAECMCus59Hvi+sUhtZTccitMmXRYeH+hZpt61RidFRLWzwe nxAWvPxLtU9HC0Pc+zYBWQIZAP8ks93ruPqtoczsmiK+YSoyU+I4bKxM/wIZAMAx 2S5sDr/R+mizU6c8KnSRyQ60jY8HLQIZALZ8b9F4ObPB4IoLaCsVc7WUjX6t0Lxj zQIZAKm8nHjiF9iSwlsrXMrKWRhgFDf3fzl89QIZALgkMvFA5CmRO+DMECBMsxIb kjBF/mzooA== -----END RSA PRIVATE KEY----- """ # KEY_PEM_PKCS8_* and KEY_PEM_PKCS5_* contain the same private key, but in # different formats. # PKCS#5 RSA unencrypted. # Generated with: # openssl genrsa -out private.pem 512 KEY_PEM_PKCS5_UNENCRYPTED = b"""-----BEGIN RSA PRIVATE KEY----- MIIBOwIBAAJBAKX6cRhPHvdyoftEHGiRje3tTLRDnddg01AvgsJJcCFoIjwdgfa9 aKFdzCcgD/htjvfRZl24M7E89sMUBMNHk8ECAwEAAQJABcBi8OO1AAAh6tIWZe09 TNRfRxPcwVzilbG/xznCP/YMf72E8hsZazu+HGMKITg9dFeJOyjXZ4e8sD/pL/I6 0QIhANzULu4JjJxpoTK8NnF/CemF7neLROA18NDB/mao5ZZtAiEAwGnYobinxuHS UQh8cT3w7aLsVlarZmCtoapxjW+ObiUCIQCcAltVV/G63vU/PrDH5hQ+opwiYIW8 UN9c3HC6XkA00QIhAJ8YpfwKgAfNfyZrmuHTspv7Q+mb3jtXoxnyodOtsxpVAiBC a4FDqkr+bDwV4SwaGdG/AC40fR3P8hhOADAhtFDwlw== -----END RSA PRIVATE KEY----- """ # PKCS#5 RSA encrypted with `test` as password. # Generated with: # openssl genrsa -des3 -out private.pem 512 KEY_PEM_PKCS5_ENCRYPTED = b"""-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,8A72BD2DC1C9092F 6LgvCNeXdcuTayEOKhQo2N4IveCP0S3t8xJCeihW9yizLeQFzSjqSfKtmRyImjfg fMl8IMDFozR+xVE9uWaIo98wKWpjyu6cytYyjL/8SP3jswBoSP5P9OekUSLifPWM ghUEu6tGissqSs/8i2wzLIdho3DdUnUMPZIprENmK6HrYmdRtJT3qMgkFTCtCS9Q r9oPm7xKPsfKBhaUHK51JcsPkPjrny8Dl56W0IYf/dfvRPwSr5yFQFLk6Nbgnx0N 32aT3ZMRCEvOTxhX1cO3f5JqYLxFAGKBFwvsulTisJ6rGYOEDSMBDwZc3sqLvt5g h0vKRPqSkylQ0W5shNg0bwbxySiRxJPBL8kWDAbJVfauArabLPuNkUNwmYhIjy7j lY0oYw2xeJ9hTUly/Zg3+DI8oYYY3z7WaxPHXEoicCE= -----END RSA PRIVATE KEY----- """ # PKCS#8 RSA encrypted with `test` as password. # Generated with pkc5 as intermediate file: # openssl genrsa -des3 -out private.pem 512 # openssl pkcs8 -topk8 -in private.pem KEY_PEM_PKCS8_ENCRYPTED = b"""-----BEGIN ENCRYPTED PRIVATE KEY----- MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIyqwWErm7rlcCAggA MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAkVu+KRbmcfWIGKzgnjjBMBIIB YI3aRS0ebuzb1Tq26/HAq8pplPu+96dM1SnRNXwH0ijmP3fLBjEDH4hB/X9H8arT xWSfKQ80+FKI07DsLQKmO+cuB12MAWPSoCNBRtLwGUiwYvlMcBp6XR4NQQ+YG/Nw OgZ1InH2w7uSnDPdxV9dZculYWzJE82IohnFVZokO2nYSEfIqr1xVQZht6lfzpx2 aRje42fpYfgkEm13w4oJKIlekzA9M4CeYku7Q4l9GDSHRmoeypMSHPI8RFV9pxub ME3AMXGcRioJ0Ic/cpmwqFaJbTVRPsqFVEsMCz1T/CQ4oLjPTWg+zkxfsPIyGj7L K3yLZmTA6IxSu+wuO/bsbqiM3x718AW6U0FHXd4zk+llu3mUfhTiMYPvN/cedv/M wsT85CHM6reIBopGMqeZD965tNEcWPGMEvXXnG71dxxgrfHFv7l/o8+moVRNIQCh EArlaXgT3MlI1jb9HoNvVNg= -----END ENCRYPTED PRIVATE KEY----- """ # RSA unencrypted # Generated with pkc5 as intermediate file: # openssl genrsa -des3 -out private.pem 512 # openssl pkcs8 -topk8 -in private.pem -nocrypt KEY_PEM_PKCS8_UNENCRYPTED = b"""-----BEGIN PRIVATE KEY----- MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEApfpxGE8e93Kh+0Qc aJGN7e1MtEOd12DTUC+CwklwIWgiPB2B9r1ooV3MJyAP+G2O99FmXbgzsTz2wxQE w0eTwQIDAQABAkAFwGLw47UAACHq0hZl7T1M1F9HE9zBXOKVsb/HOcI/9gx/vYTy GxlrO74cYwohOD10V4k7KNdnh7ywP+kv8jrRAiEA3NQu7gmMnGmhMrw2cX8J6YXu d4tE4DXw0MH+Zqjllm0CIQDAadihuKfG4dJRCHxxPfDtouxWVqtmYK2hqnGNb45u JQIhAJwCW1VX8bre9T8+sMfmFD6inCJghbxQ31zccLpeQDTRAiEAnxil/AqAB81/ Jmua4dOym/tD6ZveO1ejGfKh062zGlUCIEJrgUOqSv5sPBXhLBoZ0b8ALjR9Hc/y GE4AMCG0UPCX -----END PRIVATE KEY----- """ DH_PEM = b"""-----BEGIN DH PARAMETERS----- MIICCAKCAgEAj9/hwPNNKlQEANXqFBXViNy9nVpYlqIIHaLhoKdwAFzgYM+9hNSz FM/k+K5FS5dXrM63Zh9NgTI1M+ZRHJAxM2hhsG8AA333PN+c3exTRGwjQhU16XJg Jw/r/jYfsAyKRXqZnMuXRg+3ALEHEkvNt/vMm7Zdo6SvNIN1B2I/qEGlqFRYSgx8 dS7CRxfVf9lHE+q6HB2l/bmwZQzxvt900Dclp5DZaUnS0zS8OGsx3/QcDCZ4/4nB Gs53uDcFzb6BnsoT3oowvzCGTihHXGmS5dJaMEWwdoL1/X9ZrN3+Sex9XZufZLoO nGWjEI4yEQJbPql1iutLBR723ZDLbz9bbo86vhJJqYPJyDvxLHv6moh27PCL95JH Cg1mp4DCrjPYyd65fDvd/gP2Fa7Y+yH74CsV3LLzkxIEv9WobpxRamIljehVAdUA ZHXSLSROnc5iGNT3gJ7jcFucD7pi5MbSa1nKVc0C4Vp4f1enrVvNB/je3no0KTtN 0aY+RqJTqypmsiv6b6zdLfaZHBcqy7/qmeJu/HTIiAYhk6LW6RDFcBLJCFEujV5Y 5qjyk41M4luGVr0m41LkiXPgCPko8kiqnzDi7inVjKjrmKfspCzROw9edIdvAtfH vX0dAb0xNIpm5IRffJTVVpeZwNXuE7cNvBSZ0GY4MPkiO2dcPHeV28MCAQI= -----END DH PARAMETERS----- """ CERT_NO_NEW_LINE = b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK4oWdJCuqj2MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA wfq/eNemUKJ287E0ydVkzBxX44E6WhmnAN3oq7M881CxXLseNXHe/CRqYGpmziN5 AgMBAAGjdzB1MB0GA1UdDgQWBBQNtv8Fx7AEj4VCmX1I08mk4/viVzBGBgNVHSME PzA9gBQNtv8Fx7AEj4VCmX1I08mk4/viV6EapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCuKFnSQrqo9jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA i3qou3qkVXNKuiAFe9dBvz0nhcpAZpXrpwc9R4Qk+rirEqkdCZI1feQKBz4J3ikm -----END CERTIFICATE-----""" CERT_PEMS_NO_NEW_LINE = [ b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK94OSlzVBsWMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA vtIM2QADJDHcqxZugx7MULbenrNUFrmoMDfEaedYveWY3wBxOw642L4nFWxN/fwL AgMBAAGjdzB1MB0GA1UdDgQWBBQ4O0ZSUfTA6C+Y+QZ3MpeMhysxYjBGBgNVHSME PzA9gBQ4O0ZSUfTA6C+Y+QZ3MpeMhysxYqEapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCveDkpc1QbFjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA XwKIF+Kf4OhcqbdQp253HG2KBt/WZwvNLo/bBlkrGYwfacbGuWT8nKJG70ujdKKf -----END CERTIFICATE-----""", b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK9X9aUr9pYtMA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA v401YT8GeCt6oG076W/n7hxUsFO7sd74/4+2+4OcwMiLEp8BSRdWTk3g/tdF1YHT AgMBAAGjdzB1MB0GA1UdDgQWBBT/dStoZFKGlnfedA7gtJV1K8JYKDBGBgNVHSME PzA9gBT/dStoZFKGlnfedA7gtJV1K8JYKKEapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCvV/WlK/aWLTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA uuXLfr1DgOMNt9JGNY5mBjabj3P7ALQYglygEe+QB7d0b/mFngn/aG35TuF5aud9 -----END CERTIFICATE-----""", b"""-----BEGIN CERTIFICATE----- MIIBfDCCATagAwIBAgIJAK4oWdJCuqj2MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV BAMTC3BlbS5pbnZhbGlkMB4XDTEzMDcxNzE0NDAyMFoXDTIzMDcxNTE0NDAyMFow FjEUMBIGA1UEAxMLcGVtLmludmFsaWQwTDANBgkqhkiG9w0BAQEFAAM7ADA4AjEA wfq/eNemUKJ287E0ydVkzBxX44E6WhmnAN3oq7M881CxXLseNXHe/CRqYGpmziN5 AgMBAAGjdzB1MB0GA1UdDgQWBBQNtv8Fx7AEj4VCmX1I08mk4/viVzBGBgNVHSME PzA9gBQNtv8Fx7AEj4VCmX1I08mk4/viV6EapBgwFjEUMBIGA1UEAxMLcGVtLmlu dmFsaWSCCQCuKFnSQrqo9jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAAzEA i3qou3qkVXNKuiAFe9dBvz0nhcpAZpXrpwc9R4Qk+rirEqkdCZI1feQKBz4J3ikm -----END CERTIFICATE-----""", ] CRL_PEMS = [ b"""-----BEGIN X509 CRL----- MIIBHTCBhwIBATANBgkqhkiG9w0BAQsFADBAMRcwFQYDVQQKDA50cnVzdG1lIHYw LjQuMDElMCMGA1UECwwcVGVzdGluZyBDQSAjOW5QSzlVdHdiUXBsWVlKRxcNMDAw MTAxMDAwMDAwWhgPMzAwMDAxMDEwMDAwMDBaoBEwDzANBgNVHRQEBgIEW1he8TAN BgkqhkiG9w0BAQsFAAOBgQCFvaiM6sky2LaFU5gcI3KPdmLKiAnZRSbIkMvflsAm bFqBGT72XWeLZe9Yuf5LrXrT8V+iZzzxA3UmQn36BVSbY4ZQzOryQVh1w0mELYmO tRx8uHnC2sodKxpwelfkVkoTyUvMHLDmsfBhpNnRO5Uh7aIq/ZWt2tzxiCJHZgLD gQ== -----END X509 CRL----- """, b"""-----BEGIN X509 CRL----- MIIBRjCBsAIBATANBgkqhkiG9w0BAQsFADBAMRcwFQYDVQQKDA50cnVzdG1lIHYw LjQuMDElMCMGA1UECwwcVGVzdGluZyBDQSAjNnEtVlBkeUpVcGdaOXN6TxcNMDAw MTAxMDAwMDAwWhgPMzAwMDAxMDEwMDAwMDBaMCcwJQIUc5dlqlXTL5nQqyF72UKz 8y8sjWMXDTAwMDEwMTAwMDAwMFqgETAPMA0GA1UdFAQGAgRbWGEaMA0GCSqGSIb3 DQEBCwUAA4GBACt6+sxvplrTiaAl3pAu1/Xgj45PN6YWGpNl/h0A6///2EcfnioF tL640vzHDe6wVO7pmxRkYORsplEVWog6P+AFh3n5MYc+MFBVn94hq8OGQlE6kKJ3 Sb6rAvgbb2gPFFZr5fHTBwSpWZhuVJ32eqNdvkLBjdNgYZhFVonkVIw1 -----END X509 CRL----- """, ] KEY_PEM_RSA_PUBLIC = b"""\ -----BEGIN RSA PUBLIC KEY----- MIIBCgKCAQEAq4a0j5Za0zUrkJjWWCEXBH44L3+wWQf2VRwNA3ICUHfjwbb0aGwp 4PaqY9CS88Tzmrwn9yvfkuaxa3dTIaCaoW62C6CwdCu7O2QMFZi4H2oO9NBM2ni1 rzaKulkvZV4iPGyQSiqJqMy//DAEPVx/kyOkH9oNA62srv95gs8j83inTShhUfF6 wVtZzTKDkCiNtq9ZEXl4bJnEo4CmPhpI6AKCNxztocyKDU2rV6igIfo3UjV3U8nU DPzjzHrBPoXbzXEgY6RkmgJwzCTIkv2BYkMwafY9ogDo4e1fY6JiwWzZlSEgScZb VfLmAh4rZNe5PiQtDZiwZkvfSK7+Sxaa4QIDAQAB -----END RSA PUBLIC KEY-----""" KEY_PEM_PUBLIC = b"""\ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS +rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS EVCgJjtHAGZIm5GL/KA86KDp/CwDFMSwluowcXwDwoyinmeOY9eKyh6aY72xJh7n oLBBq1N0bWi1e2i+83txOCg4yV2oVXhBo8pYEJ8LT3el6Smxol3C1oFMVdwPgc0v Tl25XucMcG/ALE/KNY6pqC2AQ6R2ERlVgPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeu lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26 ZQIDAQAB -----END PUBLIC KEY-----""" pem-19.1.0/tests/test_core.py0000644000076500000240000003550513444257234016347 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function from itertools import combinations import certifi from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey import pem from pem._compat import text_type from .data import ( CERT_NO_NEW_LINE, CERT_PEMS, CERT_PEMS_NO_NEW_LINE, CRL_PEMS, DH_PEM, KEY_PEM_PKCS5_ENCRYPTED, KEY_PEM_PKCS5_UNENCRYPTED, KEY_PEM_PKCS8_ENCRYPTED, KEY_PEM_PKCS8_UNENCRYPTED, KEY_PEM_PUBLIC, KEY_PEM_RSA_PUBLIC, ) # SHA-1 of "test" TEST_DIGEST = ( "PEM string with SHA-1 digest " "'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'" ) class TestPEMObjects(object): def test_cert_has_correct_repr(self): """ Calling repr on a Certificate instance returns the proper string. """ cert = pem.Certificate(b"test") assert "".format(TEST_DIGEST) == repr(cert) def test_cert_has_correct_str(self): """ Calling str on a Certificate instance returns the proper string. """ cert = pem.Certificate(b"test") assert str(cert) == "test" def test_cert_req_has_correct_repr(self): """ Calling repr on a CertificateRequest instance returns the proper string. """ cert_req = pem.CertificateRequest(b"test") assert "".format(TEST_DIGEST) == repr( cert_req ) def test_sha1_hexdigest(self): """ obj.sha1_digest contains the correct digest and caches it properly. """ cert = pem.Certificate(b"test") assert ( "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" == cert.sha1_hexdigest == cert.sha1_hexdigest ) def test_as_text(self): """ obj.as_text() returns the contents as Unicode. """ cert_text = pem.Certificate(b"test").as_text() assert "test" == cert_text assert isinstance(cert_text, text_type) def test_cert_req_has_correct_str(self): """ Calling str on a CertificateRequest instance returns the proper string. """ cert_req = pem.CertificateRequest(b"test") assert str(cert_req) == "test" def test_key_has_correct_repr(self): """ Calling repr on a Key instance returns the proper string. """ key = pem.Key(b"test") assert "".format(TEST_DIGEST) == repr(key) def test_key_has_correct_str(self): """ Calling str on a Key instance returns the proper string. """ key = pem.Key(b"test") assert str(key) == "test" def test_rsa_private_key_has_correct_repr(self): """ Calling repr on a RSAPrivateKey instance returns the proper string. """ key = pem.RSAPrivateKey(b"test") assert "".format(TEST_DIGEST) == repr(key) def test_rsa_public_key_has_correct_repr(self): """ Calling repr on a RSAPublicKey instance returns the proper string. """ key = pem.RSAPublicKey(b"test") assert "".format(TEST_DIGEST) == repr(key) def test_rsa_key_has_correct_str(self): """ Calling str on a RSAPrivateKey instance returns the proper string. """ key = pem.RSAPrivateKey(b"test") assert str(key) == "test" def test_dh_params_has_correct_repr(self): """ Calling repr on a DHParameters instance returns the proper string. """ params = pem.DHParameters(b"test") assert "".format(TEST_DIGEST) == repr(params) def test_dh_params_has_correct_str(self): """ Calling str on a DHParameters instance returns the proper string. """ params = pem.DHParameters(b"test") assert str(params) == "test" def test_crl_has_correct_repr(self): """ Calling repr on a CertificateRevocationList instance returns the proper string. """ crl = pem.CertificateRevocationList(b"test") assert "".format(TEST_DIGEST) == repr( crl ) def test_crl_has_correct_str(self): """ Calling str on a CertificateRevocationList instance returns the proper string. """ crl = pem.CertificateRevocationList(b"test") assert str(crl) == "test" def test_certificate_unicode(self): """ Passing unicode to Certificate encodes the string as ASCII. """ cert = pem.Certificate(u"a string") assert cert.as_bytes() == b"a string" assert str(cert) == "a string" def test_certificate_request_unicode(self): """ Passing unicode to CertificateRequest encodes the string as ASCII. """ cert_req = pem.CertificateRequest(u"a string") assert cert_req.as_bytes() == b"a string" assert str(cert_req) == "a string" def test_key_unicode(self): """ Passing unicode to Key encodes the string as ASCII. """ key = pem.Key(u"a string") assert key.as_bytes() == b"a string" assert str(key) == "a string" def test_rsa_key_unicode(self): """ Passing unicode to RSAPrivateKey encodes the string as ASCII. """ key = pem.RSAPrivateKey(u"a string") assert key.as_bytes() == b"a string" assert str(key) == "a string" def test_dhparams_unicode_deprecated(self): """ Passing unicode to DHParameters encodes the string as ASCII. """ params = pem.DHParameters(u"a string") assert params.as_bytes() == b"a string" assert str(params) == "a string" def test_crl_unicode(self): """ Passing unicode to CertificateRevocationList encodes the string as ASCII. """ crl = pem.CertificateRevocationList(u"a string") assert crl.as_bytes() == b"a string" assert str(crl) == "a string" def test_certs_equal(self): """ Two Certificate instances with equal contents are equal. """ cert1 = pem.Certificate(b"test") cert2 = pem.Certificate(b"test") assert cert1 == cert2 assert cert2 == cert1 assert hash(cert1) == hash(cert2) def test_cert_reqs_equal(self): """ Two Certificate Request instances with equal contents are equal. """ cert_req1 = pem.CertificateRequest(b"test") cert_req2 = pem.CertificateRequest(b"test") assert cert_req1 == cert_req2 assert cert_req2 == cert_req1 assert hash(cert_req1) == hash(cert_req2) def test_keys_equal(self): """ Two Key instances with equal contents are equal and have equal hashes. """ key1 = pem.Key(b"test") key2 = pem.Key(b"test") assert key1 == key2 assert key2 == key1 assert hash(key1) == hash(key2) def test_rsa_keys_equal(self): """ Two RSAPrivateKey instances with equal contents are equal and have equal hashes. """ key1 = pem.RSAPrivateKey(b"test") key2 = pem.RSAPrivateKey(b"test") assert key1 == key2 assert key2 == key1 assert hash(key1) == hash(key2) def test_dh_params_equal(self): """ Two DHParameters instances with equal contents are equal and have equal hashes. """ params1 = pem.DHParameters(b"test") params2 = pem.DHParameters(b"test") assert params1 == params2 assert params2 == params1 assert hash(params1) == hash(params2) def test_crl_equal(self): """ Two CertificateRevocationList instances with equal contents are equal and have equal hashes. """ crl1 = pem.CertificateRevocationList(b"test") crl2 = pem.CertificateRevocationList(b"test") assert crl1 == crl2 assert crl2 == crl1 assert hash(crl1) == hash(crl2) def test_cert_contents_unequal(self): """ Two Certificate instances with unequal contents are not equal. """ cert1 = pem.Certificate(b"test1") cert2 = pem.Certificate(b"test2") assert cert1 != cert2 assert cert2 != cert1 def test_cert_req_contents_unequal(self): """ Two CertificateRequest instances with unequal contents are not equal. """ cert_req1 = pem.CertificateRequest(b"test1") cert_req2 = pem.CertificateRequest(b"test2") assert cert_req1 != cert_req2 assert cert_req2 != cert_req1 def test_crl_unequal(self): """ Two CertificateRevocationList instances with unequal contents are not equal. """ crl1 = pem.CertificateRevocationList(b"test1") crl2 = pem.CertificateRevocationList(b"test2") assert crl1 != crl2 assert crl2 != crl1 def test_different_objects_unequal(self): """ Two PEM objects of different types but with equal contents are not equal. """ c = b"test" pems = [ pem.Certificate(c), pem.CertificateRequest(c), pem.Key(c), pem.RSAPrivateKey(c), pem.CertificateRevocationList(c), ] for pem1, pem2 in combinations(pems, 2): assert not pem1 == pem2 assert pem1 != pem2 def test_incompatible_types(self): """ A PEM object is not equal to some other arbitrary object. """ cert = pem.Certificate(b"test") assert not cert == object() assert cert != object() assert object() != cert def load_rsa_key(key, password=None): return serialization.load_pem_private_key( key.as_bytes(), password=password, backend=default_backend() ) class TestParse(object): """ Tests for parsing input with one or multiple PEM objects. """ def test_key_pkcs5_unencrypted(self): """ It can load an unencrypted PKCS#5 RSA key as PEM string as an RSAPrivateKey. """ rv = pem.parse(KEY_PEM_PKCS5_UNENCRYPTED) key, = rv assert isinstance(key, pem.RSAPrivateKey) assert KEY_PEM_PKCS5_UNENCRYPTED == key.as_bytes() crypto_key = load_rsa_key(key) assert isinstance(crypto_key, RSAPrivateKey) assert 512, crypto_key.key_size() def test_key_pkcs5_encrypted(self): """ It can load an encrypted PKCS#5 RSA key as PEM string as an RSAPrivateKey. """ rv = pem.parse(KEY_PEM_PKCS5_ENCRYPTED) key, = rv assert isinstance(key, pem.RSAPrivateKey) assert KEY_PEM_PKCS5_ENCRYPTED == key.as_bytes() crypto_key = load_rsa_key(key, password=b"test") assert isinstance(crypto_key, RSAPrivateKey) assert 512, crypto_key.key_size() def test_key_pkcs8_unencrypted(self): """ It can load an unencrypted PKCS#8 RSA key as PEM string as an Key. """ rv = pem.parse(KEY_PEM_PKCS8_UNENCRYPTED) key, = rv assert isinstance(key, pem.Key) assert KEY_PEM_PKCS8_UNENCRYPTED == key.as_bytes() crypto_key = load_rsa_key(key) assert isinstance(crypto_key, RSAPrivateKey) assert 512, crypto_key.key_size() def test_key_pkcs8_encrypted(self): """ It can load an encrypted PKCS#8 RSA key as PEM string as an Key. """ rv = pem.parse(KEY_PEM_PKCS8_ENCRYPTED) key, = rv assert isinstance(key, pem.Key) assert KEY_PEM_PKCS8_ENCRYPTED == key.as_bytes() crypto_key = load_rsa_key(key, password=b"test") assert isinstance(crypto_key, RSAPrivateKey) assert 512, crypto_key.key_size() def test_certificates(self): """ Parses a PEM string with multiple certificates into a list of corresponding Certificates. """ certs = pem.parse(b"".join(CERT_PEMS)) assert all(isinstance(c, pem.Certificate) for c in certs) assert CERT_PEMS == [cert.as_bytes() for cert in certs] def test_certificate_no_new_line(self): """ Parses a PEM string without a new line at the end """ cert, = pem.parse(CERT_NO_NEW_LINE) assert isinstance(cert, pem.Certificate) assert CERT_NO_NEW_LINE == cert.as_bytes() def test_certificates_no_new_line(self): """ Parses a PEM string with multiple certificates without a new line at the end into a list of corresponding Certificates. """ certs = pem.parse(b"".join(CERT_PEMS_NO_NEW_LINE)) assert all(isinstance(c, pem.Certificate) for c in certs) assert CERT_PEMS_NO_NEW_LINE == [cert.as_bytes() for cert in certs] def test_dh(self): """ Parses a PEM string with with DH parameters into a DHParameters. """ rv = pem.parse(DH_PEM) dh, = rv assert isinstance(dh, pem.DHParameters) assert DH_PEM == dh.as_bytes() def test_crl(self): """ Parses a PEM string with multiple certificate revocation lists into a list of corresponding CertificateRevocationLists """ crls = pem.parse(b"".join(CRL_PEMS)) assert all(isinstance(c, pem.CertificateRevocationList) for c in crls) assert CRL_PEMS == [crl.as_bytes() for crl in crls] def test_file(self, tmpdir): """ A file with multiple certificate PEMs is parsed into a list of corresponding Certificates. """ certs_file = tmpdir.join("certs.pem") certs_file.write(b"".join(CERT_PEMS)) certs = pem.parse_file(str(certs_file)) assert all(isinstance(c, pem.Certificate) for c in certs) assert CERT_PEMS == [cert.as_bytes() for cert in certs] def test_loads_certifi(self): """ Loading certifi returns a list of Certificates. """ cas = pem.parse_file(certifi.where()) assert isinstance(cas, list) assert all(isinstance(ca, pem.Certificate) for ca in cas) def test_allows_lf(self): """ \n and \r\n are treated equal. """ lf_pem = KEY_PEM_PKCS5_UNENCRYPTED.replace(b"\n", b"\r\n") rv, = pem.parse(lf_pem) assert rv.as_bytes() == lf_pem def test_rsa_public_key(self): """ Detects and loads RSA public keys. """ key = pem.parse(KEY_PEM_RSA_PUBLIC)[0] assert isinstance(key, pem.PublicKey) assert isinstance(key, pem.RSAPublicKey) def test_generic_public_key(self): """ Detects and loads generic public keys. """ key = pem.parse(KEY_PEM_PUBLIC)[0] assert isinstance(key, pem.PublicKey) pem-19.1.0/tests/test_twisted.py0000644000076500000240000001704713362300244017070 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function import pytest from OpenSSL import crypto from pretend import call, call_recorder, stub from twisted.internet import ssl import pem from pem.twisted import certificateOptionsFromFiles from .data import CERT_PEMS, DH_PEM, KEY_PEM, KEY_PEM2 @pytest.fixture def keyCertChainDHFile(tmpdir): """ Returns a file containing the key, three certificates, and DH parameters. """ pemFile = tmpdir.join("key_cert_and_chain_and_params.pem") pemFile.write(KEY_PEM + b"".join(CERT_PEMS) + DH_PEM) return pemFile @pytest.fixture def keyCertChainFile(tmpdir): """ Returns a file containing the key and three certificates. """ pemFile = tmpdir.join("key_cert_and_chain.pem") pemFile.write(KEY_PEM + b"".join(CERT_PEMS)) return pemFile class TestCertificateOptionsFromFiles(object): def test_worksWithoutChain(self, tmpdir): """ Creating CO without chain certificates works. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM) certFile = tmpdir.join("cert.pem") certFile.write(CERT_PEMS[0]) ctxFactory = certificateOptionsFromFiles(str(keyFile), str(certFile)) assert [] == ctxFactory.extraCertChain def test_worksWithChainInExtraFile(self, tmpdir): """ Chain can be in a separate file. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM) certFile = tmpdir.join("cert.pem") certFile.write(CERT_PEMS[0]) chainFile = tmpdir.join("chain.pem") chainFile.write(b"".join(CERT_PEMS[1:])) ctxFactory = certificateOptionsFromFiles( str(keyFile), str(certFile), str(chainFile) ) assert 2 == len(ctxFactory.extraCertChain) def test_worksWithChainInSameFile(self, tmpdir): """ Chain can be in the same file as the certificate. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM) certFile = tmpdir.join("cert_and_chain.pem") certFile.write(b"".join(CERT_PEMS)) ctxFactory = certificateOptionsFromFiles(str(keyFile), str(certFile)) assert 2 == len(ctxFactory.extraCertChain) def test_useTypesNotOrdering(self, tmpdir): """ L{pem.certificateOptionsFromFiles} identifies the chain, key, and certificate for Twisted's L{CertificateOptions} based on their types and certificate fingerprints, not their order within the file. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM) certFile = tmpdir.join("cert_and_chain.pem") certFile.write(b"".join(reversed(CERT_PEMS))) ctxFactory = certificateOptionsFromFiles(str(keyFile), str(certFile)) assert 2 == len(ctxFactory.extraCertChain) def test_worksWithEverythingInOneFile(self, keyCertChainDHFile): """ Key, certificate, and chain can also be in a single file. """ ctxFactory = certificateOptionsFromFiles(str(keyCertChainDHFile)) assert 2 == len(ctxFactory.extraCertChain) assert ctxFactory.dhParameters is not None def test_passesCertsInCorrectFormat(self, keyCertChainDHFile): """ PEM objects are correctly detected and passed into CO. """ ctxFactory = certificateOptionsFromFiles(str(keyCertChainDHFile)) assert isinstance(ctxFactory.privateKey, crypto.PKey) assert isinstance(ctxFactory.certificate, crypto.X509) assert all( isinstance(cert, crypto.X509) for cert in ctxFactory.extraCertChain ) def test_forwardsKWargs(self, keyCertChainDHFile): """ Extra keyword arguments are passed into CO. """ ctxFactory = certificateOptionsFromFiles( str(keyCertChainDHFile), fixBrokenPeers=True ) assert True is ctxFactory.fixBrokenPeers def test_catchesMissingKey(self, tmpdir): """ Raises ValueError if a key is missing. """ certFile = tmpdir.join("cert_and_chain.pem") certFile.write(b"".join(CERT_PEMS)) with pytest.raises(ValueError): certificateOptionsFromFiles(str(certFile)) def test_catchesMultipleKeys(self, tmpdir): """ Raises ValueError if multiple keys are present. """ allFile = tmpdir.join("key_cert_and_chain.pem") allFile.write(KEY_PEM + b"".join(CERT_PEMS) + KEY_PEM2) with pytest.raises(ValueError): certificateOptionsFromFiles(str(allFile)) def test_catchesMissingCertificate(self, tmpdir): """ Raises ValueError if no certificate is passed. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM) with pytest.raises(ValueError): certificateOptionsFromFiles(str(keyFile)) def test_catchesKeyCertificateMismatch(self, tmpdir): """ A ValueError is raised when some certificates are present in the pem, but no certificate in the pem matches the key. """ keyFile = tmpdir.join("key.pem") keyFile.write(KEY_PEM + b"".join(CERT_PEMS[1:])) with pytest.raises(ValueError) as excinfo: certificateOptionsFromFiles(str(keyFile)) assert str(excinfo.value).startswith("No certificate matching ") def test_catchesMultipleDHParams(self, tmpdir): """ A ValueError is raised when more than one set of DH parameters is present. """ pemFile = tmpdir.join("multiple_params.pem") pemFile.write(KEY_PEM + CERT_PEMS[0] + DH_PEM + DH_PEM) with pytest.raises(ValueError) as excinfo: certificateOptionsFromFiles(str(pemFile)) assert ( "Supplied PEM file(s) contain(s) *more* than one set of DH " "parameters." ) == str(excinfo.value) def test_removedLegacyDHParameterSupport(self, keyCertChainFile): """ Passing dhParameters as an argument raises a TypeError. """ fakeParameters = object() with pytest.raises(TypeError, match="Passing DH parameters"): certificateOptionsFromFiles( str(keyCertChainFile), dhParameters=fakeParameters ) class _TestForwardCompatibleDHE(object): def test_realDHParameterFileSupport(self, monkeypatch, keyCertChainDHFile): """ Pass DH parameters loaded from a file directly to CertificateOptions if the installed version of Twisted supports it. """ fakeCtxFactory = object() recorder = call_recorder(lambda *a, **kw: fakeCtxFactory) monkeypatch.setattr(ssl, "CertificateOptions", recorder) monkeypatch.setattr(pem.twisted, "_DH_PARAMETERS_SUPPORTED", True) ctxFactory = certificateOptionsFromFiles(str(keyCertChainDHFile)) assert ctxFactory is fakeCtxFactory assert isinstance( recorder.calls[0].kwargs["dhParameters"], pem.twisted.DiffieHellmanParameters, ) def test_DHParamContextFactory(self): """ ContextFactory is wrapped and DH params loaded. """ fakeContext = stub(load_tmp_dh=call_recorder(lambda dhParams: None)) fakeFactory = stub(getContext=lambda: fakeContext) fakeDH = stub(path=b"foo") ctxFactory = pem.twisted._DHParamContextFactory( fakeFactory, pem.twisted._DiffieHellmanParameters(fakeDH) ) ctx = ctxFactory.getContext() assert fakeContext is ctx assert [call(b"foo")] == fakeContext.load_tmp_dh.calls pem-19.1.0/tox.ini0000644000076500000240000000303713444254014014143 0ustar hynekstaff00000000000000[tox] envlist = lint,mypy,{py27,py34,py35,py36,py37,pypy,pypy3}{-twisted,},manifest,docs,pypi-description,coverage-report isolated_build = true [testenv:lint] description = Run all pre-commit hooks. basepython = python3.7 skip_install = true deps = pre-commit passenv = HOMEPATH # needed on Windows commands = pre-commit run --all-files [testenv] description = Run tests and measure coverage. extras = tests deps = twisted: twisted[tls] commands = {py27,py37}{-twisted,}: coverage run --parallel -m pytest {posargs} {py34,py35,py36,pypy,pypy3}{-twisted,}: python -m pytest {posargs} [testenv:mypy] description = Check types basepython = python3.7 extras = tests deps = mypy commands = mypy src [testenv:manifest] description = Ensure MANIFEST.in is up to date. basepython = python3.7 deps = check-manifest commands = check-manifest [testenv:docs] description = Build docs and run doctests. basepython = python3.7 extras = docs deps = twisted[tls] commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build/html sphinx-build -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html [testenv:pypi-description] description = Ensure README.rst renders on PyPI. basepython = python3.7 skip_install = true deps = twine pip >= 18.0.0 commands = pip wheel -w {envtmpdir}/build --no-deps . twine check {envtmpdir}/build/* [testenv:coverage-report] description = Report coverage over all test runs. basepython = python3.7 deps = coverage skip_install = true commands = coverage combine coverage report