pem-19.1.0/ 0000755 0000765 0000024 00000000000 13444265115 012631 5 ustar hynek staff 0000000 0000000 pem-19.1.0/.coveragerc 0000644 0000765 0000024 00000000244 12654641726 014762 0 ustar hynek staff 0000000 0000000 [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.yaml 0000644 0000765 0000024 00000001325 13444240740 017110 0 ustar hynek staff 0000000 0000000 repos:
- 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.yml 0000644 0000765 0000024 00000000173 13444254041 015714 0 ustar hynek staff 0000000 0000000 ---
version: 2
python:
version: 3.7
install:
- method: pip
path: .
extra_requirements:
- docs
pem-19.1.0/.travis.yml 0000644 0000765 0000024 00000003013 13444261773 014745 0 ustar hynek staff 0000000 0000000 dist: 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.rst 0000644 0000765 0000024 00000000426 12654671265 014523 0 ustar hynek staff 0000000 0000000 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 `_.
pem-19.1.0/CHANGELOG.rst 0000644 0000765 0000024 00000011045 13444264276 014662 0 ustar hynek staff 0000000 0000000 .. :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.rst 0000644 0000765 0000024 00000006262 13311141106 015630 0 ustar hynek staff 0000000 0000000 Contributor 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.rst 0000644 0000765 0000024 00000015354 13444261463 015304 0 ustar hynek staff 0000000 0000000 How 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/LICENSE 0000644 0000765 0000024 00000002050 12455220311 013621 0 ustar hynek staff 0000000 0000000 Copyright (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.in 0000644 0000765 0000024 00000000467 13444237427 014403 0 ustar hynek staff 0000000 0000000 include *.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-INFO 0000644 0000765 0000024 00000012535 13444265115 013734 0 ustar hynek staff 0000000 0000000 Metadata-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.rst 0000644 0000765 0000024 00000005015 13313410011 014277 0 ustar hynek staff 0000000 0000000 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
pem-19.1.0/codecov.yml 0000644 0000765 0000024 00000000257 13311650213 014770 0 ustar hynek staff 0000000 0000000 ---
comment: false
coverage:
status:
patch:
default:
target: "100"
project:
default:
target: "100"
pem-19.1.0/conftest.py 0000644 0000765 0000024 00000000211 13311142012 015001 0 ustar hynek staff 0000000 0000000 collect_ignore = []
try:
import twisted.internet.ssl # noqa
except ImportError:
collect_ignore.append("tests/test_twisted.py")
pem-19.1.0/docs/ 0000755 0000765 0000024 00000000000 13444265115 013561 5 ustar hynek staff 0000000 0000000 pem-19.1.0/docs/Makefile 0000644 0000765 0000024 00000016345 12547453400 015231 0 ustar hynek staff 0000000 0000000 # 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.rst 0000644 0000765 0000024 00000001721 13444257077 015075 0 ustar hynek staff 0000000 0000000 .. _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.rst 0000644 0000765 0000024 00000001041 12547713355 020743 0 ustar hynek staff 0000000 0000000 Backward 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.rst 0000644 0000765 0000024 00000000036 12654656557 016261 0 ustar hynek staff 0000000 0000000 .. include:: ../CHANGELOG.rst
pem-19.1.0/docs/conf.py 0000644 0000765 0000024 00000024320 13313404231 015046 0 ustar hynek staff 0000000 0000000 # -*- 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.rst 0000644 0000765 0000024 00000000064 12547473757 017042 0 ustar hynek staff 0000000 0000000 .. _contributing:
.. include:: ../CONTRIBUTING.rst
pem-19.1.0/docs/core.rst 0000644 0000765 0000024 00000001606 13444262367 015254 0 ustar hynek staff 0000000 0000000 Core 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.rst 0000644 0000765 0000024 00000001020 12547744026 015421 0 ustar hynek staff 0000000 0000000 =====================================
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.rst 0000644 0000765 0000024 00000000477 13143056101 015732 0 ustar hynek staff 0000000 0000000 License 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.bat 0000644 0000765 0000024 00000016106 12547453400 015171 0 ustar hynek staff 0000000 0000000 @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.rst 0000644 0000765 0000024 00000003756 13311234023 015774 0 ustar hynek staff 0000000 0000000 Twisted
=======
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.ini 0000644 0000765 0000024 00000000536 13313410011 014312 0 ustar hynek staff 0000000 0000000 [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.toml 0000644 0000765 0000024 00000000202 13430224357 015535 0 ustar hynek staff 0000000 0000000 [build-system]
requires = ["setuptools>=40.6.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 79
pem-19.1.0/setup.cfg 0000644 0000765 0000024 00000000661 13444265115 014455 0 ustar hynek staff 0000000 0000000 [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.py 0000644 0000765 0000024 00000006726 13420046611 014346 0 ustar hynek staff 0000000 0000000 import 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/ 0000755 0000765 0000024 00000000000 13444265115 013420 5 ustar hynek staff 0000000 0000000 pem-19.1.0/src/pem/ 0000755 0000765 0000024 00000000000 13444265115 014201 5 ustar hynek staff 0000000 0000000 pem-19.1.0/src/pem/__init__.py 0000644 0000765 0000024 00000001554 13444264276 016326 0 ustar hynek staff 0000000 0000000 from __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.py 0000644 0000765 0000024 00000000500 13313410011 016146 0 ustar hynek staff 0000000 0000000 from __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.py 0000644 0000765 0000024 00000011145 13444256752 015653 0 ustar hynek staff 0000000 0000000 # -*- 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.typed 0000644 0000765 0000024 00000000000 13313410011 015644 0 ustar hynek staff 0000000 0000000 pem-19.1.0/src/pem/twisted.py 0000644 0000765 0000024 00000006712 13313410011 016222 0 ustar hynek staff 0000000 0000000 # -*- 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/ 0000755 0000765 0000024 00000000000 13444265115 015673 5 ustar hynek staff 0000000 0000000 pem-19.1.0/src/pem.egg-info/PKG-INFO 0000644 0000765 0000024 00000012535 13444265115 016776 0 ustar hynek staff 0000000 0000000 Metadata-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.txt 0000644 0000765 0000024 00000001404 13444265115 017556 0 ustar hynek staff 0000000 0000000 .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.py pem-19.1.0/src/pem.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 13444265115 021741 0 ustar hynek staff 0000000 0000000
pem-19.1.0/src/pem.egg-info/not-zip-safe 0000644 0000765 0000024 00000000001 12654656744 020137 0 ustar hynek staff 0000000 0000000
pem-19.1.0/src/pem.egg-info/requires.txt 0000644 0000765 0000024 00000000222 13444265115 020267 0 ustar hynek staff 0000000 0000000
[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.txt 0000644 0000765 0000024 00000000004 13444265115 020417 0 ustar hynek staff 0000000 0000000 pem
pem-19.1.0/tests/ 0000755 0000765 0000024 00000000000 13444265115 013773 5 ustar hynek staff 0000000 0000000 pem-19.1.0/tests/__init__.py 0000644 0000765 0000024 00000000000 12547506557 016105 0 ustar hynek staff 0000000 0000000 pem-19.1.0/tests/data.py 0000644 0000765 0000024 00000024747 13444256117 015276 0 ustar hynek staff 0000000 0000000 CERT_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.py 0000644 0000765 0000024 00000035505 13444257234 016347 0 ustar hynek staff 0000000 0000000 # -*- 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.py 0000644 0000765 0000024 00000017047 13362300244 017070 0 ustar hynek staff 0000000 0000000 # -*- 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.ini 0000644 0000765 0000024 00000003037 13444254014 014143 0 ustar hynek staff 0000000 0000000 [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