././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2434218 josepy-1.10.0/0000755000076500000240000000000000000000000011553 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/.coveragerc0000644000076500000240000000024200000000000013672 0ustar00bmwstaff[run] branch = True source = josepy [paths] source = .tox/*/lib/python*/site-packages/josepy .tox/pypy*/site-packages/josepy [report] show_missing = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/CHANGELOG.rst0000644000076500000240000000445600000000000013605 0ustar00bmwstaffChangelog ========= 1.10.0 (2021-09-27) ------------------- * josepy is now compliant with PEP-561: type checkers will fetch types from the inline types annotations when josepy is installed as a dependency in a Python project. * Added a `field` function to assist in adding type annotations for Fields in classes. If the field function is used to define a `Field` in a `JSONObjectWithFields` based class without a type annotation, an error will be raised. * josepy's tests can no longer be imported under the name josepy, however, they are still included in the package and you can run them by installing josepy with "tests" extras and running `python -m pytest`. 1.9.0 (2021-09-09) ------------------ * Removed pytest-cache testing dependency. * Fixed a bug that sometimes caused incorrect padding to be used when serializing Elliptic Curve keys as JSON Web Keys. 1.8.0 (2021-03-15) ------------------ * Removed external mock dependency. * Removed dependency on six. * Deprecated the module josepy.magic_typing. * Fix JWS/JWK generation with EC keys when keys or signatures have leading zeros. 1.7.0 (2021-02-11) ------------------ * Dropped support for Python 2.7. * Added support for EC keys. 1.6.0 (2021-01-26) ------------------ * Deprecated support for Python 2.7. 1.5.0 (2020-11-03) ------------------ * Added support for Python 3.9. * Dropped support for Python 3.5. * Stopped supporting running tests with ``python setup.py test`` which is deprecated in favor of ``python -m pytest``. 1.4.0 (2020-08-17) ------------------ * Deprecated support for Python 3.5. 1.3.0 (2020-01-28) ------------------ * Deprecated support for Python 3.4. * Officially add support for Python 3.8. 1.2.0 (2019-06-28) ------------------ * Support for Python 2.6 and 3.3 has been removed. * Known incompatibilities with Python 3.8 have been resolved. 1.1.0 (2018-04-13) ------------------ * Deprecated support for Python 2.6 and 3.3. * Use the ``sign`` and ``verify`` methods when they are available in ``cryptography`` instead of the deprecated methods ``signer`` and ``verifier``. 1.0.1 (2017-10-25) ------------------ Stop installing mock as part of the default but only as part of the testing dependencies. 1.0.0 (2017-10-13) ------------------- First release after moving the josepy package into a standalone library. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/CONTRIBUTING.md0000644000076500000240000000257200000000000014012 0ustar00bmwstaff # Certbot Contributing Guide Hi! Welcome to the Certbot project. We look forward to collaborating with you. If you're reporting a bug in Certbot, please make sure to include: - The version of Certbot you're running. - The operating system you're running it on. - The commands you ran. - What you expected to happen, and - What actually happened. If you're a developer, we have some helpful information in our [Developer's Guide](https://certbot.eff.org/docs/contributing.html) to get you started. In particular, we recommend you read these sections - [Finding issues to work on](https://certbot.eff.org/docs/contributing.html#find-issues-to-work-on) - [Coding style](https://certbot.eff.org/docs/contributing.html#coding-style) - [Submitting a pull request](https://certbot.eff.org/docs/contributing.html#submitting-a-pull-request) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/LICENSE.txt0000644000076500000240000002504200000000000013401 0ustar00bmwstaff Copyright 2015 Electronic Frontier Foundation and others Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/MANIFEST.in0000644000076500000240000000031300000000000013306 0ustar00bmwstaffinclude CHANGELOG.rst include CONTRIBUTING.md include LICENSE.txt include README.rst include .coveragerc pytest.ini tox.ini include src/josepy/py.typed recursive-include docs * recursive-include tests * ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2434738 josepy-1.10.0/PKG-INFO0000644000076500000240000000270700000000000012656 0ustar00bmwstaffMetadata-Version: 2.1 Name: josepy Version: 1.10.0 Summary: JOSE protocol implementation in Python Home-page: https://github.com/certbot/josepy Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Requires-Python: >=3.6 Provides-Extra: dev Provides-Extra: docs Provides-Extra: tests License-File: LICENSE.txt JOSE protocol implementation in Python using cryptography .. image:: https://github.com/certbot/josepy/actions/workflows/check.yaml/badge.svg :target: https://github.com/certbot/josepy/actions/workflows/check.yaml .. image:: https://codecov.io/gh/certbot/josepy/branch/master/graph/badge.svg :target: https://codecov.io/gh/certbot/josepy .. image:: https://readthedocs.org/projects/josepy/badge/?version=latest :target: http://josepy.readthedocs.io/en/latest/?badge=latest Originally developed as part of the ACME_ protocol implementation. .. _ACME: https://pypi.python.org/pypi/acme ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/README.rst0000644000076500000240000000112300000000000013237 0ustar00bmwstaffJOSE protocol implementation in Python using cryptography .. image:: https://github.com/certbot/josepy/actions/workflows/check.yaml/badge.svg :target: https://github.com/certbot/josepy/actions/workflows/check.yaml .. image:: https://codecov.io/gh/certbot/josepy/branch/master/graph/badge.svg :target: https://codecov.io/gh/certbot/josepy .. image:: https://readthedocs.org/projects/josepy/badge/?version=latest :target: http://josepy.readthedocs.io/en/latest/?badge=latest Originally developed as part of the ACME_ protocol implementation. .. _ACME: https://pypi.python.org/pypi/acme ././@PaxHeader0000000000000000000000000000003200000000000010210 xustar0026 mtime=1632778604.23753 josepy-1.10.0/docs/0000755000076500000240000000000000000000000012503 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/.gitignore0000644000076500000240000000001100000000000014463 0ustar00bmwstaff/_build/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/Makefile0000644000076500000240000000113700000000000014145 0ustar00bmwstaff# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python -msphinx SPHINXPROJ = josepy SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2376337 josepy-1.10.0/docs/_static/0000755000076500000240000000000000000000000014131 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/_static/.gitignore0000644000076500000240000000000000000000000016107 0ustar00bmwstaff././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2377167 josepy-1.10.0/docs/_templates/0000755000076500000240000000000000000000000014640 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/_templates/.gitignore0000644000076500000240000000000000000000000016616 0ustar00bmwstaff././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2385626 josepy-1.10.0/docs/api/0000755000076500000240000000000000000000000013254 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/base64.rst0000644000076500000240000000010100000000000015062 0ustar00bmwstaffJOSE Base64 ----------- .. automodule:: josepy.b64 :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/errors.rst0000644000076500000240000000007200000000000015321 0ustar00bmwstaffErrors ------ .. automodule:: josepy.errors :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/interfaces.rst0000644000076500000240000000010600000000000016126 0ustar00bmwstaffInterfaces ---------- .. automodule:: josepy.interfaces :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/json_util.rst0000644000076500000240000000011500000000000016011 0ustar00bmwstaffJSON utilities -------------- .. automodule:: josepy.json_util :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/jwa.rst0000644000076500000240000000012100000000000014561 0ustar00bmwstaffJSON Web Algorithms ------------------- .. automodule:: josepy.jwa :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/jwk.rst0000644000076500000240000000010300000000000014573 0ustar00bmwstaffJSON Web Key ------------ .. automodule:: josepy.jwk :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/jws.rst0000644000076500000240000000011700000000000014610 0ustar00bmwstaffJSON Web Signature ------------------ .. automodule:: josepy.jws :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/api/util.rst0000644000076500000240000000007600000000000014766 0ustar00bmwstaffUtilities --------- .. automodule:: josepy.util :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/changelog.rst0000644000076500000240000000003600000000000015163 0ustar00bmwstaff.. include:: ../CHANGELOG.rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778601.0 josepy-1.10.0/docs/conf.py0000644000076500000240000001321100000000000014000 0ustar00bmwstaff# -*- coding: utf-8 -*- # # josepy documentation build configuration file, created by # sphinx-quickstart on Wed Oct 11 17:05:53 2017. # # 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. # 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. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', ] autodoc_member_order = 'bysource' autodoc_default_flags = ['show-inheritance', 'private-members'] # 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 master toctree document. master_doc = 'index' # General information about the project. project = u'josepy' copyright = u"2015-2017, Let's Encrypt Project" author = u"Let's Encrypt Project" # 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 = u'1.10' # The full version, including alpha/beta/rc tags. release = u'1.10.0' # 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 # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # The reST default role (used for this markup: `text`) to use for all # documents. default_role = 'py:obj' # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # 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 static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'about.html', 'navigation.html', 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', 'donate.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'josepydoc' # -- 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, 'josepy.tex', u'josepy Documentation', u"Let's Encrypt Project", 'manual'), ] # -- 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, 'josepy', u'josepy Documentation', [author], 1), ('man/jws', 'jws', u'jws script documentation', [project], 1), ] # -- 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, 'josepy', u'josepy Documentation', author, 'josepy', 'One line description of project.', 'Miscellaneous'), ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'https://docs.python.org/': None, 'https://cryptography.io/en/latest/': None, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/index.rst0000644000076500000240000000034600000000000014347 0ustar00bmwstaffjosepy ====== .. automodule:: josepy :members: .. toctree:: :maxdepth: 2 :caption: Contents: :glob: api/* changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/jws-help.txt0000644000076500000240000000024700000000000015000 0ustar00bmwstaffusage: jws [-h] [--compact] {sign,verify} ... positional arguments: {sign,verify} optional arguments: -h, --help show this help message and exit --compact ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2386777 josepy-1.10.0/docs/man/0000755000076500000240000000000000000000000013256 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/man/jws.rst0000644000076500000240000000004400000000000014611 0ustar00bmwstaff.. literalinclude:: ../jws-help.txt ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/docs/requirements.txt0000644000076500000240000000001300000000000015761 0ustar00bmwstaff-e .[docs] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/pytest.ini0000644000076500000240000000032600000000000013605 0ustar00bmwstaff[pytest] addopts = -v --flake8 --cov-report xml --cov-report=term-missing --cov=josepy --cov-config .coveragerc filterwarnings = error norecursedirs = *.egg .eggs dist build docs .tox flake8-ignore = W504 E501 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2437081 josepy-1.10.0/setup.cfg0000644000076500000240000000010300000000000013366 0ustar00bmwstaff[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778601.0 josepy-1.10.0/setup.py0000644000076500000240000000405300000000000013267 0ustar00bmwstaffimport io from setuptools import find_packages, setup version = '1.10.0' # Please update tox.ini when modifying dependency version requirements install_requires = [ # load_pem_private/public_key (>=0.6) # rsa_recover_prime_factors (>=0.8) 'cryptography>=0.8', # Connection.set_tlsext_host_name (>=0.13) 'PyOpenSSL>=0.13', # For pkg_resources. >=1.0 so pip resolves it to a version cryptography # will tolerate; see #2599: 'setuptools>=1.0', ] testing_requires = [ 'coverage>=4.0', 'flake8', 'mypy', 'pytest-cov', 'pytest-flake8>=0.5', 'pytest>=2.8.0', ] dev_extras = [ 'pytest', 'tox', ] docs_extras = [ 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags 'sphinx_rtd_theme', ] with io.open('README.rst', encoding='UTF-8') as f: long_description = f.read() setup( name='josepy', version=version, description='JOSE protocol implementation in Python', long_description=long_description, url='https://github.com/certbot/josepy', author="Certbot Project", author_email='client-dev@letsencrypt.org', license='Apache License 2.0', python_requires='>=3.6', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Security', ], packages=find_packages(where='src'), package_dir={'': 'src'}, include_package_data=True, install_requires=install_requires, extras_require={ 'dev': dev_extras, 'docs': docs_extras, 'tests': testing_requires, }, entry_points={ 'console_scripts': [ 'jws = josepy.jws:CLI.run', ], }, ) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1632778604.235758 josepy-1.10.0/src/0000755000076500000240000000000000000000000012342 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2397606 josepy-1.10.0/src/josepy/0000755000076500000240000000000000000000000013653 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/__init__.py0000644000076500000240000000326300000000000015770 0ustar00bmwstaff"""Javascript Object Signing and Encryption (JOSE). This package is a Python implementation of the standards developed by IETF `Javascript Object Signing and Encryption (Active WG)`_, in particular the following RFCs: - `JSON Web Algorithms (JWA)`_ - `JSON Web Key (JWK)`_ - `JSON Web Signature (JWS)`_ Originally developed as part of the ACME_ protocol implementation. .. _`Javascript Object Signing and Encryption (Active WG)`: https://tools.ietf.org/wg/jose/ .. _`JSON Web Algorithms (JWA)`: https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-algorithms/ .. _`JSON Web Key (JWK)`: https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-key/ .. _`JSON Web Signature (JWS)`: https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-signature/ .. _ACME: https://pypi.python.org/pypi/acme """ # flake8: noqa from josepy.b64 import ( b64decode, b64encode, ) from josepy.errors import ( DeserializationError, SerializationError, Error, UnrecognizedTypeError, ) from josepy.interfaces import JSONDeSerializable from josepy.json_util import ( Field, JSONObjectWithFields, TypedJSONObjectWithFields, decode_b64jose, decode_cert, decode_csr, decode_hex16, encode_b64jose, encode_cert, encode_csr, encode_hex16, field, ) from josepy.jwa import ( HS256, HS384, HS512, JWASignature, PS256, PS384, PS512, RS256, RS384, RS512, ES256, ES384, ES512, ) from josepy.jwk import ( JWK, JWKRSA, ) from josepy.jws import ( Header, JWS, Signature, ) from josepy.util import ( ComparableX509, ComparableKey, ComparableRSAKey, ImmutableMap, ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/b64.py0000644000076500000240000000267500000000000014632 0ustar00bmwstaff"""`JOSE Base64`_ is defined as: - URL-safe Base64 - padding stripped .. _`JOSE Base64`: https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-37#appendix-C .. Do NOT try to call this module "base64", as it will "shadow" the standard library. """ import base64 def b64encode(data: bytes) -> bytes: """JOSE Base64 encode. :param data: Data to be encoded. :type data: bytes :returns: JOSE Base64 string. :rtype: bytes :raises TypeError: if ``data`` is of incorrect type """ if not isinstance(data, bytes): raise TypeError('argument should be bytes') return base64.urlsafe_b64encode(data).rstrip(b'=') def b64decode(data: bytes) -> bytes: """JOSE Base64 decode. :param data: Base64 string to be decoded. If it's unicode, then only ASCII characters are allowed. :type data: bytes or unicode :returns: Decoded data. :rtype: bytes :raises TypeError: if input is of incorrect type :raises ValueError: if input is unicode with non-ASCII characters """ if isinstance(data, str): try: data = data.encode('ascii') except UnicodeEncodeError: raise ValueError( 'unicode argument should contain only ASCII characters') elif not isinstance(data, bytes): raise TypeError('argument should be a str or unicode') return base64.urlsafe_b64decode(data + b'=' * (4 - (len(data) % 4))) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/errors.py0000644000076500000240000000146100000000000015543 0ustar00bmwstaff"""JOSE errors.""" from typing import Any class Error(Exception): """Generic JOSE Error.""" class DeserializationError(Error): """JSON deserialization error.""" def __str__(self) -> str: return "Deserialization error: {0}".format( super().__str__()) class SerializationError(Error): """JSON serialization error.""" class UnrecognizedTypeError(DeserializationError): """Unrecognized type error. :ivar str typ: The unrecognized type of the JSON object. :ivar jobj: Full JSON object. """ def __init__(self, typ: str, jobj: Any) -> None: self.typ = typ self.jobj = jobj super().__init__(str(self)) def __str__(self) -> str: return '{0} was not recognized, full message: {1}'.format( self.typ, self.jobj) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/interfaces.py0000644000076500000240000001721000000000000016351 0ustar00bmwstaff"""JOSE interfaces.""" import abc import json from typing import Any from josepy import errors from collections.abc import Sequence, Mapping # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class # pylint: disable=too-few-public-methods class JSONDeSerializable(object, metaclass=abc.ABCMeta): # pylint: disable=too-few-public-methods """Interface for (de)serializable JSON objects. Please recall, that standard Python library implements :class:`json.JSONEncoder` and :class:`json.JSONDecoder` that perform translations based on respective :ref:`conversion tables ` that look pretty much like the one below (for complete tables see relevant Python documentation): .. _conversion-table: ====== ====== JSON Python ====== ====== object dict ... ... ====== ====== While the above **conversion table** is about translation of JSON documents to/from the basic Python types only, :class:`JSONDeSerializable` introduces the following two concepts: serialization Turning an arbitrary Python object into Python object that can be encoded into a JSON document. **Full serialization** produces a Python object composed of only basic types as required by the :ref:`conversion table `. **Partial serialization** (accomplished by :meth:`to_partial_json`) produces a Python object that might also be built from other :class:`JSONDeSerializable` objects. deserialization Turning a decoded Python object (necessarily one of the basic types as required by the :ref:`conversion table `) into an arbitrary Python object. Serialization produces **serialized object** ("partially serialized object" or "fully serialized object" for partial and full serialization respectively) and deserialization produces **deserialized object**, both usually denoted in the source code as ``jobj``. Wording in the official Python documentation might be confusing after reading the above, but in the light of those definitions, one can view :meth:`json.JSONDecoder.decode` as decoder and deserializer of basic types, :meth:`json.JSONEncoder.default` as serializer of basic types, :meth:`json.JSONEncoder.encode` as serializer and encoder of basic types. One could extend :mod:`json` to support arbitrary object (de)serialization either by: - overriding :meth:`json.JSONDecoder.decode` and :meth:`json.JSONEncoder.default` in subclasses - or passing ``object_hook`` argument (or ``object_hook_pairs``) to :func:`json.load`/:func:`json.loads` or ``default`` argument for :func:`json.dump`/:func:`json.dumps`. Interestingly, ``default`` is required to perform only partial serialization, as :func:`json.dumps` applies ``default`` recursively. This is the idea behind making :meth:`to_partial_json` produce only partial serialization, while providing custom :meth:`json_dumps` that dumps with ``default`` set to :meth:`json_dump_default`. To make further documentation a bit more concrete, please, consider the following imaginatory implementation example:: class Foo(JSONDeSerializable): def to_partial_json(self): return 'foo' @classmethod def from_json(cls, jobj): return Foo() class Bar(JSONDeSerializable): def to_partial_json(self): return [Foo(), Foo()] @classmethod def from_json(cls, jobj): return Bar() """ @abc.abstractmethod def to_partial_json(self) -> Any: # pragma: no cover """Partially serialize. Following the example, **partial serialization** means the following:: assert isinstance(Bar().to_partial_json()[0], Foo) assert isinstance(Bar().to_partial_json()[1], Foo) # in particular... assert Bar().to_partial_json() != ['foo', 'foo'] :raises josepy.errors.SerializationError: in case of any serialization error. :returns: Partially serializable object. """ raise NotImplementedError() def to_json(self) -> Any: """Fully serialize. Again, following the example from before, **full serialization** means the following:: assert Bar().to_json() == ['foo', 'foo'] :raises josepy.errors.SerializationError: in case of any serialization error. :returns: Fully serialized object. """ def _serialize(obj: Any) -> Any: if isinstance(obj, JSONDeSerializable): return _serialize(obj.to_partial_json()) if isinstance(obj, str): # strings are Sequence return obj elif isinstance(obj, list): return [_serialize(subobj) for subobj in obj] elif isinstance(obj, Sequence): # default to tuple, otherwise Mapping could get # unhashable list return tuple(_serialize(subobj) for subobj in obj) elif isinstance(obj, Mapping): return {_serialize(key): _serialize(value) for key, value in obj.items()} else: return obj return _serialize(self) @classmethod @abc.abstractmethod def from_json(cls, jobj: Any) -> 'JSONDeSerializable': """Deserialize a decoded JSON document. :param jobj: Python object, composed of only other basic data types, as decoded from JSON document. Not necessarily :class:`dict` (as decoded from "JSON object" document). :raises josepy.errors.DeserializationError: if decoding was unsuccessful, e.g. in case of unparseable X509 certificate, or wrong padding in JOSE base64 encoded string, etc. """ # TypeError: Can't instantiate abstract class with # abstract methods from_json, to_partial_json return cls() # pylint: disable=abstract-class-instantiated @classmethod def json_loads(cls, json_string: str) -> 'JSONDeSerializable': """Deserialize from JSON document string.""" try: loads = json.loads(json_string) except ValueError as error: raise errors.DeserializationError(error) return cls.from_json(loads) def json_dumps(self, **kwargs: Any) -> str: """Dump to JSON string using proper serializer. :returns: JSON document string. :rtype: str """ return json.dumps(self, default=self.json_dump_default, **kwargs) def json_dumps_pretty(self) -> str: """Dump the object to pretty JSON document string. :rtype: str """ return self.json_dumps(sort_keys=True, indent=4, separators=(',', ': ')) @classmethod def json_dump_default(cls, python_object: 'JSONDeSerializable') -> Any: """Serialize Python object. This function is meant to be passed as ``default`` to :func:`json.dump` or :func:`json.dumps`. They call ``default(python_object)`` only for non-basic Python types, so this function necessarily raises :class:`TypeError` if ``python_object`` is not an instance of :class:`IJSONSerializable`. Please read the class docstring for more information. """ if isinstance(python_object, JSONDeSerializable): return python_object.to_partial_json() else: # this branch is necessary, cannot just "return" raise TypeError(repr(python_object) + ' is not JSON serializable') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/json_util.py0000644000076500000240000004374400000000000016247 0ustar00bmwstaff"""JSON (de)serialization framework. The framework presented here is somewhat based on `Go's "json" package`_ (especially the ``omitempty`` functionality). .. _`Go's "json" package`: http://golang.org/pkg/encoding/json/ """ import abc import binascii import logging from typing import Dict, Type, Any, Callable, List, Mapping, Optional from OpenSSL import crypto import josepy.util from josepy import b64, errors, interfaces, util logger = logging.getLogger(__name__) def field(json_name: str, default: Any = None, omitempty: bool = False, decoder: Callable[[Any], Any] = None, encoder: Callable[[Any], Any] = None) -> Any: """Convenient function to declare a :class:`Field` with proper type annotations. This function allows to write the following code: import josepy class JSON(josepy.JSONObjectWithFields): typ: str = josepy.field('type') def other_type(self) -> str: return self.typ """ return _TypedField(json_name=json_name, default=default, omitempty=omitempty, decoder=decoder, encoder=encoder) class Field: """JSON object field. :class:`Field` is meant to be used together with :class:`JSONObjectWithFields`. ``encoder`` (``decoder``) is a callable that accepts a single parameter, i.e. a value to be encoded (decoded), and returns the serialized (deserialized) value. In case of errors it should raise :class:`~josepy.errors.SerializationError` (:class:`~josepy.errors.DeserializationError`). Note, that ``decoder`` should perform partial serialization only. :ivar str json_name: Name of the field when encoded to JSON. :ivar default: Default value (used when not present in JSON object). :ivar bool omitempty: If ``True`` and the field value is empty, then it will not be included in the serialized JSON object, and ``default`` will be used for deserialization. Otherwise, if ``False``, field is considered as required, value will always be included in the serialized JSON objected, and it must also be present when deserializing. """ __slots__ = ('json_name', 'default', 'omitempty', 'fdec', 'fenc') def __init__(self, json_name: str, default: Any = None, omitempty: bool = False, decoder: Callable[[Any], Any] = None, encoder: Callable[[Any], Any] = None) -> None: # pylint: disable=too-many-arguments self.json_name = json_name self.default = default self.omitempty = omitempty self.fdec = self.default_decoder if decoder is None else decoder self.fenc = self.default_encoder if encoder is None else encoder @classmethod def _empty(cls, value: Any) -> bool: """Is the provided value considered "empty" for this field? This is useful for subclasses that might want to override the definition of being empty, e.g. for some more exotic data types. """ return not isinstance(value, bool) and not value def omit(self, value: Any) -> bool: """Omit the value in output?""" return self._empty(value) and self.omitempty def _update_params(self, **kwargs: Any) -> 'Field': current = { "json_name": self.json_name, "default": self.default, "omitempty": self.omitempty, "decoder": self.fdec, "encoder": self.fenc, **kwargs, } return type(self)(**current) # type: ignore[arg-type] # pylint: disable=star-args def decoder(self, fdec: Callable[[Any], Any]) -> 'Field': """Descriptor to change the decoder on JSON object field.""" return self._update_params(decoder=fdec) def encoder(self, fenc: Callable[[Any], Any]) -> 'Field': """Descriptor to change the encoder on JSON object field.""" return self._update_params(encoder=fenc) def decode(self, value: Any) -> Any: """Decode a value, optionally with context JSON object.""" return self.fdec(value) def encode(self, value: Any) -> Any: """Encode a value, optionally with context JSON object.""" return self.fenc(value) @classmethod def default_decoder(cls, value: Any) -> Any: """Default decoder. Recursively deserialize into immutable types ( :class:`josepy.util.frozendict` instead of :func:`dict`, :func:`tuple` instead of :func:`list`). """ # bases cases for different types returned by json.loads if isinstance(value, list): return tuple(cls.default_decoder(subvalue) for subvalue in value) elif isinstance(value, dict): return util.frozendict( {cls.default_decoder(key): cls.default_decoder(value) for key, value in value.items()}) else: # integer or string return value @classmethod def default_encoder(cls, value: Any) -> Any: """Default (passthrough) encoder.""" # field.to_partial_json() is no good as encoder has to do partial # serialization only return value class _TypedField(Field): """Specialized class to mark a JSON object field with typed annotations. This class is kept private because fields are supposed to be declared using the :function:`field` in this situation. In the future the :class:`Field` may be removed in favor of this one.""" class JSONObjectWithFieldsMeta(abc.ABCMeta): """Metaclass for :class:`JSONObjectWithFields` and its subclasses. It makes sure that, for any class ``cls`` with ``__metaclass__`` set to ``JSONObjectWithFieldsMeta``: 1. All fields (attributes of type :class:`Field`) in the class definition are moved to the ``cls._fields`` dictionary, where keys are field attribute names and values are fields themselves. 2. ``cls.__slots__`` is extended by all field attribute names (i.e. not :attr:`Field.json_name`). Original ``cls.__slots__`` are stored in ``cls._orig_slots``. In a consequence, for a field attribute name ``some_field``, ``cls.some_field`` will be a slot descriptor and not an instance of :class:`Field`. For example:: some_field = Field('someField', default=()) class Foo: __metaclass__ = JSONObjectWithFieldsMeta __slots__ = ('baz',) some_field = some_field assert Foo.__slots__ == ('some_field', 'baz') assert Foo._orig_slots == () assert Foo.some_field is not Field assert Foo._fields.keys() == ['some_field'] assert Foo._fields['some_field'] is some_field As an implementation note, this metaclass inherits from :class:`abc.ABCMeta` (and not the usual :class:`type`) to mitigate the metaclass conflict (:class:`ImmutableMap` and :class:`JSONDeSerializable`, parents of :class:`JSONObjectWithFields`, use :class:`abc.ABCMeta` as its metaclass). """ _fields: Dict[str, Field] = {} def __new__(mcs, name: str, bases: List[str], namespace: Dict[str, Any]) -> 'JSONObjectWithFieldsMeta': fields = {} for base in bases: fields.update(getattr(base, '_fields', {})) # Do not reorder, this class might override fields from base classes! # We use a copy of namespace in the loop because the loop modifies it. for key, value in namespace.copy().items(): if isinstance(value, Field): if isinstance(value, _TypedField): # Ensure the type annotation has been set for the field. # Error out if it is not the case. if key not in namespace.get('__annotations__', {}): raise ValueError( f'Field `{key}` in JSONObject `{name}` has no type annotation.') fields[key] = namespace.pop(key) namespace['_orig_slots'] = namespace.get('__slots__', ()) namespace['__slots__'] = tuple( list(namespace['_orig_slots']) + list(fields.keys())) namespace['_fields'] = fields return abc.ABCMeta.__new__(mcs, name, bases, namespace) # type: ignore[call-overload] class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable, metaclass=JSONObjectWithFieldsMeta): # pylint: disable=too-few-public-methods """JSON object with fields. Example:: class Foo(JSONObjectWithFields): bar = Field('Bar') empty = Field('Empty', omitempty=True) @bar.encoder def bar(value): return value + 'bar' @bar.decoder def bar(value): if not value.endswith('bar'): raise errors.DeserializationError('No bar suffix!') return value[:-3] assert Foo(bar='baz').to_partial_json() == {'Bar': 'bazbar'} assert Foo.from_json({'Bar': 'bazbar'}) == Foo(bar='baz') assert (Foo.from_json({'Bar': 'bazbar', 'Empty': '!'}) == Foo(bar='baz', empty='!')) assert Foo(bar='baz').bar == 'baz' """ @classmethod def _defaults(cls) -> Dict[str, Any]: """Get default fields values.""" return { slot: field.default for slot, field in cls._fields.items() } def __init__(self, **kwargs: Any) -> None: # pylint: disable=star-args super().__init__( **{**self._defaults(), **kwargs}) def encode(self, name: str) -> Any: """Encode a single field. :param str name: Name of the field to be encoded. :raises errors.SerializationError: if field cannot be serialized :raises errors.Error: if field could not be found """ try: field = self._fields[name] # type: ignore[attr-defined] except KeyError: raise errors.Error("Field not found: {0}".format(name)) return field.encode(getattr(self, name)) def fields_to_partial_json(self) -> Dict[str, Any]: """Serialize fields to JSON.""" jobj = {} omitted = set() for slot, field in self._fields.items(): # type: ignore[attr-defined] value = getattr(self, slot) if field.omit(value): omitted.add((slot, value)) else: try: jobj[field.json_name] = field.encode(value) except errors.SerializationError as error: raise errors.SerializationError( 'Could not encode {0} ({1}): {2}'.format( slot, value, error)) return jobj def to_partial_json(self) -> Dict[str, Any]: return self.fields_to_partial_json() @classmethod def _check_required(cls, jobj: Mapping[str, Any]) -> None: missing = set() for _, field in cls._fields.items(): if not field.omitempty and field.json_name not in jobj: missing.add(field.json_name) if missing: raise errors.DeserializationError( 'The following fields are required: {0}'.format( ','.join(missing))) @classmethod def fields_from_json(cls, jobj: Mapping[str, Any]) -> Any: """Deserialize fields from JSON.""" cls._check_required(jobj) fields = {} for slot, field in cls._fields.items(): if field.json_name not in jobj and field.omitempty: fields[slot] = field.default else: value = jobj[field.json_name] try: fields[slot] = field.decode(value) except errors.DeserializationError as error: raise errors.DeserializationError( 'Could not decode {0!r} ({1!r}): {2}'.format( slot, value, error)) return fields @classmethod def from_json(cls, jobj: Dict[str, Any]) -> 'JSONObjectWithFields': return cls(**cls.fields_from_json(jobj)) def encode_b64jose(data: bytes) -> str: """Encode JOSE Base-64 field. :param bytes data: :rtype: `str` """ # b64encode produces ASCII characters only return b64.b64encode(data).decode('ascii') def decode_b64jose(data: str, size: Optional[int] = None, minimum: bool = False) -> bytes: """Decode JOSE Base-64 field. :param unicode data: :param int size: Required length (after decoding). :param bool minimum: If ``True``, then `size` will be treated as minimum required length, as opposed to exact equality. :rtype: bytes """ try: decoded = b64.b64decode(data.encode()) except binascii.Error as error: raise errors.DeserializationError(error) if size is not None and ((not minimum and len(decoded) != size) or (minimum and len(decoded) < size)): raise errors.DeserializationError( "Expected at least or exactly {0} bytes".format(size)) return decoded def encode_hex16(value: bytes) -> str: """Hexlify. :param bytes value: :rtype: unicode """ return binascii.hexlify(value).decode() def decode_hex16(value: str, size: Optional[int] = None, minimum: bool = False) -> bytes: """Decode hexlified field. :param unicode value: :param int size: Required length (after decoding). :param bool minimum: If ``True``, then `size` will be treated as minimum required length, as opposed to exact equality. :rtype: bytes """ value_b = value.encode() if size is not None and ((not minimum and len(value_b) != size * 2) or (minimum and len(value_b) < size * 2)): raise errors.DeserializationError() try: return binascii.unhexlify(value_b) except binascii.Error as error: raise errors.DeserializationError(error) def encode_cert(cert: josepy.util.ComparableX509) -> str: """Encode certificate as JOSE Base-64 DER. :type cert: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` :rtype: unicode """ if isinstance(cert.wrapped, crypto.X509Req): raise ValueError("Error input is actually a certificate request.") return encode_b64jose(crypto.dump_certificate( crypto.FILETYPE_ASN1, cert.wrapped)) def decode_cert(b64der: str) -> josepy.util.ComparableX509: """Decode JOSE Base-64 DER-encoded certificate. :param unicode b64der: :rtype: `OpenSSL.crypto.X509` wrapped in `.ComparableX509` """ try: return util.ComparableX509(crypto.load_certificate( crypto.FILETYPE_ASN1, decode_b64jose(b64der))) except crypto.Error as error: raise errors.DeserializationError(error) def encode_csr(csr: josepy.util.ComparableX509) -> str: """Encode CSR as JOSE Base-64 DER. :type csr: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509` :rtype: unicode """ if isinstance(csr.wrapped, crypto.X509): raise ValueError("Error input is actually a certificate.") return encode_b64jose(crypto.dump_certificate_request( crypto.FILETYPE_ASN1, csr.wrapped)) def decode_csr(b64der: str) -> josepy.util.ComparableX509: """Decode JOSE Base-64 DER-encoded CSR. :param unicode b64der: :rtype: `OpenSSL.crypto.X509Req` wrapped in `.ComparableX509` """ try: return util.ComparableX509(crypto.load_certificate_request( crypto.FILETYPE_ASN1, decode_b64jose(b64der))) except crypto.Error as error: raise errors.DeserializationError(error) class TypedJSONObjectWithFields(JSONObjectWithFields): """JSON object with type.""" typ: str = NotImplemented """Type of the object. Subclasses must override.""" type_field_name: str = "type" """Field name used to distinguish different object types. Subclasses will probably have to override this. """ TYPES: Dict[str, Type] = NotImplemented """Types registered for JSON deserialization""" @classmethod def register(cls, type_cls: Type['TypedJSONObjectWithFields'], typ: Optional[str] = None) -> Type['TypedJSONObjectWithFields']: """Register class for JSON deserialization.""" typ = type_cls.typ if typ is None else typ cls.TYPES[typ] = type_cls return type_cls @classmethod def get_type_cls(cls, jobj: Mapping[str, Any]) -> Type['TypedJSONObjectWithFields']: """Get the registered class for ``jobj``.""" if cls in cls.TYPES.values(): if cls.type_field_name not in jobj: raise errors.DeserializationError( "Missing type field ({0})".format(cls.type_field_name)) # cls is already registered type_cls, force to use it # so that, e.g Revocation.from_json(jobj) fails if # jobj["type"] != "revocation". return cls if not isinstance(jobj, dict): raise errors.DeserializationError( "{0} is not a dictionary object".format(jobj)) try: typ = jobj[cls.type_field_name] except KeyError: raise errors.DeserializationError("missing type field") try: return cls.TYPES[typ] except KeyError: raise errors.UnrecognizedTypeError(typ, jobj) def to_partial_json(self) -> Dict[str, Any]: """Get JSON serializable object. :returns: Serializable JSON object representing ACME typed object. :meth:`validate` will almost certainly not work, due to reasons explained in :class:`josepy.interfaces.IJSONSerializable`. :rtype: dict """ jobj = self.fields_to_partial_json() jobj[self.type_field_name] = self.typ return jobj @classmethod def from_json(cls, jobj: Mapping[str, Any]) -> 'TypedJSONObjectWithFields': """Deserialize ACME object from valid JSON object. :raises josepy.errors.UnrecognizedTypeError: if type of the ACME object has not been registered. """ # make sure subclasses don't cause infinite recursive from_json calls type_cls = cls.get_type_cls(jobj) return type_cls(**type_cls.fields_from_json(jobj)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/jwa.py0000644000076500000240000002162400000000000015013 0ustar00bmwstaff"""JSON Web Algorithms. https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40 """ import abc import logging from typing import Dict, Any, Callable import cryptography.exceptions from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hmac from cryptography.hazmat.primitives.asymmetric import padding, ec, rsa from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature from cryptography.hazmat.primitives.hashes import HashAlgorithm from josepy import errors, interfaces, jwk from collections.abc import Hashable logger = logging.getLogger(__name__) class JWA(interfaces.JSONDeSerializable): # pylint: disable=abstract-method # pylint: disable=too-few-public-methods # for some reason disable=abstract-method has to be on the line # above... """JSON Web Algorithm.""" class JWASignature(JWA, Hashable): """Base class for JSON Web Signature Algorithms.""" SIGNATURES: Dict[str, 'JWASignature'] = {} kty: Any def __init__(self, name: str) -> None: self.name = name def __eq__(self, other: Any) -> bool: if not isinstance(other, JWASignature): return NotImplemented return self.name == other.name def __hash__(self) -> int: return hash((self.__class__, self.name)) @classmethod def register(cls, signature_cls: 'JWASignature') -> 'JWASignature': """Register class for JSON deserialization.""" cls.SIGNATURES[signature_cls.name] = signature_cls return signature_cls def to_partial_json(self) -> Any: return self.name @classmethod def from_json(cls, jobj: Any) -> 'JWASignature': return cls.SIGNATURES[jobj] @abc.abstractmethod def sign(self, key: Any, msg: bytes) -> bytes: # pragma: no cover """Sign the ``msg`` using ``key``.""" raise NotImplementedError() @abc.abstractmethod def verify(self, key: Any, msg: bytes, sig: bytes) -> bool: # pragma: no cover """Verify the ``msg`` and ``sig`` using ``key``.""" raise NotImplementedError() def __repr__(self) -> str: return self.name class _JWAHS(JWASignature): kty = jwk.JWKOct def __init__(self, name: str, hash_: Callable[[], HashAlgorithm]): super().__init__(name) self.hash = hash_() def sign(self, key: bytes, msg: bytes) -> bytes: signer = hmac.HMAC(key, self.hash, backend=default_backend()) signer.update(msg) return signer.finalize() def verify(self, key: bytes, msg: bytes, sig: bytes) -> bool: verifier = hmac.HMAC(key, self.hash, backend=default_backend()) verifier.update(msg) try: verifier.verify(sig) except cryptography.exceptions.InvalidSignature as error: logger.debug(error, exc_info=True) return False else: return True class _JWARSA: kty = jwk.JWKRSA padding: Any = NotImplemented hash: HashAlgorithm = NotImplemented def sign(self, key: rsa.RSAPrivateKey, msg: bytes) -> bytes: """Sign the ``msg`` using ``key``.""" # If cryptography library supports new style api (v1.4 and later) new_api = hasattr(key, "sign") try: if new_api: return key.sign(msg, self.padding, self.hash) signer = key.signer(self.padding, self.hash) except AttributeError as error: logger.debug(error, exc_info=True) raise errors.Error("Public key cannot be used for signing") except ValueError as error: # digest too large logger.debug(error, exc_info=True) raise errors.Error(str(error)) signer.update(msg) try: return signer.finalize() except ValueError as error: logger.debug(error, exc_info=True) raise errors.Error(str(error)) def verify(self, key: rsa.RSAPublicKey, msg: bytes, sig: bytes) -> bool: """Verify the ``msg` and ``sig`` using ``key``.""" # If cryptography library supports new style api (v1.4 and later) new_api = hasattr(key, "verify") if not new_api: verifier = key.verifier(sig, self.padding, self.hash) verifier.update(msg) try: if new_api: key.verify(sig, msg, self.padding, self.hash) else: verifier.verify() except cryptography.exceptions.InvalidSignature as error: logger.debug(error, exc_info=True) return False else: return True class _JWARS(_JWARSA, JWASignature): def __init__(self, name: str, hash_: Callable[[], HashAlgorithm]) -> None: super().__init__(name) self.padding = padding.PKCS1v15() self.hash = hash_() class _JWAPS(_JWARSA, JWASignature): def __init__(self, name: str, hash_: Callable[[], HashAlgorithm]) -> None: super().__init__(name) self.padding = padding.PSS( mgf=padding.MGF1(hash_()), salt_length=padding.PSS.MAX_LENGTH) self.hash = hash_() class _JWAEC(JWASignature): kty = jwk.JWKEC def __init__(self, name: str, hash_: Callable[[], HashAlgorithm]): super().__init__(name) self.hash = hash_() def sign(self, key: ec.EllipticCurvePrivateKey, msg: bytes) -> bytes: """Sign the ``msg`` using ``key``.""" sig = self._sign(key, msg) dr, ds = decode_dss_signature(sig) length = jwk.JWKEC.expected_length_for_curve(key.curve) return (dr.to_bytes(length=length, byteorder='big') + ds.to_bytes(length=length, byteorder='big')) def _sign(self, key: ec.EllipticCurvePrivateKey, msg: bytes) -> bytes: # If cryptography library supports new style api (v1.4 and later) new_api = hasattr(key, 'sign') try: if new_api: return key.sign(msg, ec.ECDSA(self.hash)) signer = key.signer(ec.ECDSA(self.hash)) except AttributeError as error: logger.debug(error, exc_info=True) raise errors.Error('Public key cannot be used for signing') except ValueError as error: # digest too large logger.debug(error, exc_info=True) raise errors.Error(str(error)) signer.update(msg) try: return signer.finalize() except ValueError as error: logger.debug(error, exc_info=True) raise errors.Error(str(error)) def verify(self, key: ec.EllipticCurvePublicKey, msg: bytes, sig: bytes) -> bool: """Verify the ``msg` and ``sig`` using ``key``.""" rlen = jwk.JWKEC.expected_length_for_curve(key.curve) if len(sig) != 2 * rlen: # Format error - rfc7518 - 3.4 … MUST NOT be shortened to omit any leading zero octets return False asn1sig = encode_dss_signature( int.from_bytes(sig[0:rlen], byteorder='big'), int.from_bytes(sig[rlen:], byteorder='big') ) return self._verify(key, msg, asn1sig) def _verify(self, key: ec.EllipticCurvePublicKey, msg: bytes, asn1sig: bytes) -> bool: # If cryptography library supports new style api (v1.4 and later) new_api = hasattr(key, 'verify') if not new_api: verifier = key.verifier(asn1sig, ec.ECDSA(self.hash)) verifier.update(msg) try: if new_api: key.verify(asn1sig, msg, ec.ECDSA(self.hash)) else: verifier.verify() except cryptography.exceptions.InvalidSignature as error: logger.debug(error, exc_info=True) return False else: return True #: HMAC using SHA-256 HS256 = JWASignature.register(_JWAHS('HS256', hashes.SHA256)) #: HMAC using SHA-384 HS384 = JWASignature.register(_JWAHS('HS384', hashes.SHA384)) #: HMAC using SHA-512 HS512 = JWASignature.register(_JWAHS('HS512', hashes.SHA512)) #: RSASSA-PKCS-v1_5 using SHA-256 RS256 = JWASignature.register(_JWARS('RS256', hashes.SHA256)) #: RSASSA-PKCS-v1_5 using SHA-384 RS384 = JWASignature.register(_JWARS('RS384', hashes.SHA384)) #: RSASSA-PKCS-v1_5 using SHA-512 RS512 = JWASignature.register(_JWARS('RS512', hashes.SHA512)) #: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 PS256 = JWASignature.register(_JWAPS('PS256', hashes.SHA256)) #: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 PS384 = JWASignature.register(_JWAPS('PS384', hashes.SHA384)) #: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 PS512 = JWASignature.register(_JWAPS('PS512', hashes.SHA512)) #: ECDSA using P-256 and SHA-256 ES256 = JWASignature.register(_JWAEC('ES256', hashes.SHA256)) #: ECDSA using P-384 and SHA-384 ES384 = JWASignature.register(_JWAEC('ES384', hashes.SHA384)) #: ECDSA using P-521 and SHA-512 ES512 = JWASignature.register(_JWAEC('ES512', hashes.SHA512)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/jwk.py0000644000076500000240000003360200000000000015024 0ustar00bmwstaff"""JSON Web Key.""" import abc import json import logging import math from typing import Dict, Optional, Sequence, Type, Union, Callable, Any, Tuple, Mapping import cryptography.exceptions import josepy.util from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import rsa from josepy import errors, json_util, util logger = logging.getLogger(__name__) class JWK(json_util.TypedJSONObjectWithFields, metaclass=abc.ABCMeta): # pylint: disable=too-few-public-methods """JSON Web Key.""" type_field_name = 'kty' TYPES: Dict[str, Type['JWK']] = {} cryptography_key_types: Tuple[Type[Any], ...] = () """Subclasses should override.""" required: Sequence[str] = NotImplemented """Required members of public key's representation as defined by JWK/JWA.""" _thumbprint_json_dumps_params: Dict[str, Union[Optional[int], Sequence[str], bool]] = { # "no whitespace or line breaks before or after any syntactic # elements" 'indent': None, 'separators': (',', ':'), # "members ordered lexicographically by the Unicode [UNICODE] # code points of the member names" 'sort_keys': True, } key: Any def thumbprint(self, hash_function: Callable[[], hashes.HashAlgorithm] = hashes.SHA256) -> bytes: """Compute JWK Thumbprint. https://tools.ietf.org/html/rfc7638 :returns: bytes """ digest = hashes.Hash(hash_function(), backend=default_backend()) digest.update(json.dumps( {k: v for k, v in self.to_json().items() if k in self.required}, **self._thumbprint_json_dumps_params).encode()) # type: ignore[arg-type] return digest.finalize() @abc.abstractmethod def public_key(self) -> 'JWK': # pragma: no cover """Generate JWK with public key. For symmetric cryptosystems, this would return ``self``. """ raise NotImplementedError() @classmethod def _load_cryptography_key(cls, data: bytes, password: Optional[bytes] = None, backend: Optional[Any] = None) -> Any: backend = default_backend() if backend is None else backend exceptions = {} # private key? for loader_private in (serialization.load_pem_private_key, serialization.load_der_private_key): try: return loader_private(data, password, backend) except (ValueError, TypeError, cryptography.exceptions.UnsupportedAlgorithm) as error: exceptions[str(loader_private)] = error # public key? for loader_public in (serialization.load_pem_public_key, serialization.load_der_public_key): try: return loader_public(data, backend) except (ValueError, cryptography.exceptions.UnsupportedAlgorithm) as error: exceptions[str(loader_public)] = error # no luck raise errors.Error('Unable to deserialize key: {0}'.format(exceptions)) @classmethod def load(cls, data: bytes, password: Optional[bytes] = None, backend: Optional[Any] = None) -> 'JWK': """Load serialized key as JWK. :param str data: Public or private key serialized as PEM or DER. :param str password: Optional password. :param backend: A `.PEMSerializationBackend` and `.DERSerializationBackend` provider. :raises errors.Error: if unable to deserialize, or unsupported JWK algorithm :returns: JWK of an appropriate type. :rtype: `JWK` """ try: key = cls._load_cryptography_key(data, password, backend) except errors.Error as error: logger.debug('Loading symmetric key, asymmetric failed: %s', error) return JWKOct(key=data) if cls.typ is not NotImplemented and not isinstance(key, cls.cryptography_key_types): raise errors.Error('Unable to deserialize {0} into {1}'.format( key.__class__, cls.__class__)) for jwk_cls in cls.TYPES.values(): if isinstance(key, jwk_cls.cryptography_key_types): return jwk_cls(key=key) raise errors.Error('Unsupported algorithm: {0}'.format(key.__class__)) @JWK.register class JWKOct(JWK): """Symmetric JWK.""" typ = 'oct' __slots__ = ('key',) required = ('k', JWK.type_field_name) key: bytes def fields_to_partial_json(self) -> Dict[str, str]: # TODO: An "alg" member SHOULD also be present to identify the # algorithm intended to be used with the key, unless the # application uses another means or convention to determine # the algorithm used. return {'k': json_util.encode_b64jose(self.key)} @classmethod def fields_from_json(cls, jobj: Mapping[str, Any]) -> 'JWKOct': return cls(key=json_util.decode_b64jose(jobj['k'])) def public_key(self) -> 'JWKOct': return self @JWK.register class JWKRSA(JWK): """RSA JWK. :ivar key: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` wrapped in :class:`~josepy.util.ComparableRSAKey` """ typ = 'RSA' cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey) __slots__ = ('key',) required = ('e', JWK.type_field_name, 'n') key: josepy.util.ComparableRSAKey def __init__(self, *args: Any, **kwargs: Any) -> None: if 'key' in kwargs and not isinstance( kwargs['key'], util.ComparableRSAKey): kwargs['key'] = util.ComparableRSAKey(kwargs['key']) super().__init__(*args, **kwargs) @classmethod def _encode_param(cls, data: int) -> str: """Encode Base64urlUInt. :type data: long :rtype: unicode """ length = max(data.bit_length(), 8) # decoding 0 length = math.ceil(length / 8) return json_util.encode_b64jose(data.to_bytes(byteorder="big", length=length)) @classmethod def _decode_param(cls, data: str) -> int: """Decode Base64urlUInt.""" try: binary = json_util.decode_b64jose(data) if not binary: raise errors.DeserializationError() return int.from_bytes(binary, byteorder="big") except ValueError: # invalid literal for long() with base 16 raise errors.DeserializationError() def public_key(self) -> 'JWKRSA': return type(self)(key=self.key.public_key()) @classmethod def fields_from_json(cls, jobj: Mapping[str, Any]) -> 'JWKRSA': # pylint: disable=invalid-name n, e = (cls._decode_param(jobj[x]) for x in ('n', 'e')) public_numbers = rsa.RSAPublicNumbers(e=e, n=n) # public key if 'd' not in jobj: return cls(key=public_numbers.public_key(default_backend())) # private key d = cls._decode_param(jobj['d']) if ('p' in jobj or 'q' in jobj or 'dp' in jobj or 'dq' in jobj or 'qi' in jobj or 'oth' in jobj): # "If the producer includes any of the other private # key parameters, then all of the others MUST be # present, with the exception of "oth", which MUST # only be present when more than two prime factors # were used." p, q, dp, dq, qi, = all_params = tuple( jobj.get(x) for x in ('p', 'q', 'dp', 'dq', 'qi')) if tuple(param for param in all_params if param is None): raise errors.Error( 'Some private parameters are missing: {0}'.format( all_params)) p, q, dp, dq, qi = tuple( cls._decode_param(str(x)) for x in all_params) # TODO: check for oth else: # cryptography>=0.8 p, q = rsa.rsa_recover_prime_factors(n, e, d) dp = rsa.rsa_crt_dmp1(d, p) dq = rsa.rsa_crt_dmq1(d, q) qi = rsa.rsa_crt_iqmp(p, q) key = rsa.RSAPrivateNumbers( p, q, d, dp, dq, qi, public_numbers).private_key( default_backend()) return cls(key=key) def fields_to_partial_json(self) -> Dict[str, Any]: # pylint: disable=protected-access if isinstance(self.key._wrapped, rsa.RSAPublicKey): numbers = self.key.public_numbers() params = { 'n': numbers.n, 'e': numbers.e, } else: # rsa.RSAPrivateKey private = self.key.private_numbers() public = self.key.public_key().public_numbers() params = { 'n': public.n, 'e': public.e, 'd': private.d, 'p': private.p, 'q': private.q, 'dp': private.dmp1, 'dq': private.dmq1, 'qi': private.iqmp, } return {key: self._encode_param(value) for key, value in params.items()} @JWK.register class JWKEC(JWK): """EC JWK. :ivar key: :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` or :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` wrapped in :class:`~josepy.util.ComparableECKey` """ typ = 'EC' __slots__ = ('key',) cryptography_key_types = ( ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey) required = ('crv', JWK.type_field_name, 'x', 'y') key: josepy.util.ComparableECKey def __init__(self, *args: Any, **kwargs: Any) -> None: if 'key' in kwargs and not isinstance( kwargs['key'], util.ComparableECKey): kwargs['key'] = util.ComparableECKey(kwargs['key']) super().__init__(*args, **kwargs) @classmethod def _encode_param(cls, data: int, length: int) -> str: """Encode Base64urlUInt. :type data: long :type key_size: long :rtype: unicode """ return json_util.encode_b64jose(data.to_bytes(byteorder="big", length=length)) @classmethod def _decode_param(cls, data: str, name: str, valid_length: int) -> int: """Decode Base64urlUInt.""" try: binary = json_util.decode_b64jose(data) if len(binary) != valid_length: raise errors.DeserializationError( 'Expected parameter "{name}" to be {valid_lengths} bytes ' 'after base64-decoding; got {length} bytes instead'.format( name=name, valid_lengths=valid_length, length=len(binary)) ) return int.from_bytes(binary, byteorder="big") except ValueError: # invalid literal for long() with base 16 raise errors.DeserializationError() @classmethod def _curve_name_to_crv(cls, curve_name: str) -> str: if curve_name == 'secp256r1': return 'P-256' if curve_name == 'secp384r1': return 'P-384' if curve_name == 'secp521r1': return 'P-521' raise errors.SerializationError() @classmethod def _crv_to_curve(cls, crv: str) -> ec.EllipticCurve: # crv is case-sensitive if crv == 'P-256': return ec.SECP256R1() if crv == 'P-384': return ec.SECP384R1() if crv == 'P-521': return ec.SECP521R1() raise errors.DeserializationError() @classmethod def expected_length_for_curve(cls, curve: ec.EllipticCurve) -> int: if isinstance(curve, ec.SECP256R1): return 32 elif isinstance(curve, ec.SECP384R1): return 48 elif isinstance(curve, ec.SECP521R1): return 66 raise ValueError(f'Unexpected curve: {curve}') def fields_to_partial_json(self) -> Dict[str, Any]: params = {} if isinstance(self.key._wrapped, ec.EllipticCurvePublicKey): public = self.key.public_numbers() elif isinstance(self.key._wrapped, ec.EllipticCurvePrivateKey): private = self.key.private_numbers() public = self.key.public_key().public_numbers() params['d'] = private.private_value else: raise errors.SerializationError( 'Supplied key is neither of type EllipticCurvePublicKey nor EllipticCurvePrivateKey') params['x'] = public.x params['y'] = public.y params = {key: self._encode_param(value, self.expected_length_for_curve(public.curve)) for key, value in params.items()} params['crv'] = self._curve_name_to_crv(public.curve.name) return params @classmethod def fields_from_json(cls, jobj: Mapping[str, Any]) -> 'JWKEC': # pylint: disable=invalid-name curve = cls._crv_to_curve(jobj['crv']) expected_length = cls.expected_length_for_curve(curve) x, y = (cls._decode_param(jobj[n], n, expected_length) for n in ('x', 'y')) public_numbers = ec.EllipticCurvePublicNumbers(x=x, y=y, curve=curve) # private key if 'd' not in jobj: return cls(key=public_numbers.public_key(default_backend())) # private key d = cls._decode_param(jobj['d'], 'd', expected_length) key = ec.EllipticCurvePrivateNumbers(d, public_numbers).private_key( default_backend()) return cls(key=key) def public_key(self) -> 'JWKEC': # Unlike RSAPrivateKey, EllipticCurvePrivateKey does not contain public_key() if hasattr(self.key, 'public_key'): key = self.key.public_key() else: key = self.key.public_numbers().public_key(default_backend()) return type(self)(key=key) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/jws.py0000644000076500000240000003632700000000000015043 0ustar00bmwstaff"""JSON Web Signature.""" import argparse import base64 import sys from typing import Dict, Any, Optional, FrozenSet, Mapping, List, Type, Tuple, cast from OpenSSL import crypto import josepy from josepy import b64, errors, json_util, jwa, jwk as jwk_mod, util class MediaType: """MediaType field encoder/decoder.""" PREFIX = 'application/' """MIME Media Type and Content Type prefix.""" @classmethod def decode(cls, value: str) -> str: """Decoder.""" # 4.1.10 if '/' not in value: if ';' in value: raise errors.DeserializationError('Unexpected semi-colon') return cls.PREFIX + value return value @classmethod def encode(cls, value: str) -> str: """Encoder.""" # 4.1.10 if ';' not in value: assert value.startswith(cls.PREFIX) return value[len(cls.PREFIX):] return value class Header(json_util.JSONObjectWithFields): """JOSE Header. .. warning:: This class supports **only** Registered Header Parameter Names (as defined in section 4.1 of the protocol). If you need Public Header Parameter Names (4.2) or Private Header Parameter Names (4.3), you must subclass and override :meth:`from_json` and :meth:`to_partial_json` appropriately. .. warning:: This class does not support any extensions through the "crit" (Critical) Header Parameter (4.1.11) and as a conforming implementation, :meth:`from_json` treats its occurrence as an error. Please subclass if you seek for a different behaviour. :ivar x5tS256: "x5t#S256" :ivar str typ: MIME Media Type, inc. :const:`MediaType.PREFIX`. :ivar str cty: Content-Type, inc. :const:`MediaType.PREFIX`. """ alg: jwa.JWASignature = json_util.field( 'alg', decoder=jwa.JWASignature.from_json, omitempty=True) jku: bytes = json_util.field('jku', omitempty=True) jwk: jwk_mod.JWK = json_util.field('jwk', decoder=jwk_mod.JWK.from_json, omitempty=True) kid: bytes = json_util.field('kid', omitempty=True) x5u: bytes = json_util.field('x5u', omitempty=True) x5c: Tuple[util.ComparableX509, ...] = json_util.field('x5c', omitempty=True, default=()) x5t: bytes = json_util.field( 'x5t', decoder=json_util.decode_b64jose, omitempty=True) x5tS256: bytes = json_util.field( 'x5t#S256', decoder=json_util.decode_b64jose, omitempty=True) typ: MediaType = json_util.field('typ', encoder=MediaType.encode, decoder=MediaType.decode, omitempty=True) cty: MediaType = json_util.field('cty', encoder=MediaType.encode, decoder=MediaType.decode, omitempty=True) crit: Tuple[Any, ...] = json_util.field('crit', omitempty=True, default=()) _fields: Dict[str, json_util.Field] def not_omitted(self) -> Dict[str, json_util.Field]: """Fields that would not be omitted in the JSON object.""" return {name: getattr(self, name) for name, field in self._fields.items() if not field.omit(getattr(self, name))} def __add__(self, other: Any) -> 'Header': if not isinstance(other, type(self)): raise TypeError('Header cannot be added to: {0}'.format( type(other))) not_omitted_self = self.not_omitted() not_omitted_other = other.not_omitted() if set(not_omitted_self).intersection(not_omitted_other): raise TypeError('Addition of overlapping headers not defined') not_omitted_self.update(not_omitted_other) return type(self)(**not_omitted_self) # pylint: disable=star-args def find_key(self) -> josepy.JWK: """Find key based on header. .. todo:: Supports only "jwk" header parameter lookup. :returns: (Public) key found in the header. :rtype: .JWK :raises josepy.errors.Error: if key could not be found """ if self.jwk is None: raise errors.Error('No key found') return self.jwk @crit.decoder # type: ignore def crit(unused_value: Any) -> Any: # pylint: disable=missing-docstring,no-self-argument,no-self-use raise errors.DeserializationError( '"crit" is not supported, please subclass') # x5c does NOT use JOSE Base64 (4.1.6) @x5c.encoder # type: ignore def x5c(value): # pylint: disable=missing-docstring,no-self-argument return [base64.b64encode(crypto.dump_certificate( crypto.FILETYPE_ASN1, cert.wrapped)) for cert in value] @x5c.decoder # type: ignore def x5c(value): # pylint: disable=missing-docstring,no-self-argument try: return tuple(util.ComparableX509(crypto.load_certificate( crypto.FILETYPE_ASN1, base64.b64decode(cert))) for cert in value) except crypto.Error as error: raise errors.DeserializationError(error) class Signature(json_util.JSONObjectWithFields): """JWS Signature. :ivar combined: Combined Header (protected and unprotected, :class:`Header`). :ivar unicode protected: JWS protected header (Jose Base-64 decoded). :ivar header: JWS Unprotected Header (:class:`Header`). :ivar str signature: The signature. """ header_cls = Header combined: Header __slots__ = ('combined',) protected: str = json_util.field('protected', omitempty=True, default='') header: Header = json_util.field( 'header', omitempty=True, default=header_cls(), decoder=header_cls.from_json) signature: bytes = json_util.field( 'signature', decoder=json_util.decode_b64jose, encoder=json_util.encode_b64jose) @protected.encoder # type: ignore def protected(value: str) -> str: # pylint: disable=missing-docstring,no-self-argument # wrong type guess (Signature, not bytes) | pylint: disable=no-member return json_util.encode_b64jose(value.encode('utf-8')) @protected.decoder # type: ignore def protected(value: str) -> str: # pylint: disable=missing-docstring,no-self-argument return json_util.decode_b64jose(value).decode('utf-8') def __init__(self, **kwargs: Any) -> None: if 'combined' not in kwargs: kwargs = self._with_combined(kwargs) super().__init__(**kwargs) assert self.combined.alg is not None @classmethod def _with_combined(cls, kwargs: Any) -> Dict[str, Any]: assert 'combined' not in kwargs header = kwargs.get('header', cls._fields['header'].default) protected = kwargs.get('protected', cls._fields['protected'].default) if protected: combined = header + cls.header_cls.json_loads(protected) else: combined = header kwargs['combined'] = combined return kwargs @classmethod def _msg(cls, protected: str, payload: bytes) -> bytes: return (b64.b64encode(protected.encode('utf-8')) + b'.' + b64.b64encode(payload)) def verify(self, payload: bytes, key: Optional[josepy.JWK] = None) -> bool: """Verify. :param bytes payload: Payload to verify. :param JWK key: Key used for verification. """ actual_key: josepy.JWK = self.combined.find_key() if key is None else key return self.combined.alg.verify( key=actual_key.key, sig=self.signature, msg=self._msg(self.protected, payload)) @classmethod def sign(cls, payload: bytes, key: josepy.JWK, alg: josepy.JWASignature, include_jwk: bool = True, protect: FrozenSet = frozenset(), **kwargs: Any) -> 'Signature': """Sign. :param bytes payload: Payload to sign. :param JWK key: Key for signature. :param JWASignature alg: Signature algorithm to use to sign. :param bool include_jwk: If True, insert the JWK inside the signature headers. :param FrozenSet protect: List of headers to protect. """ assert isinstance(key, alg.kty) header_params = kwargs header_params['alg'] = alg if include_jwk: header_params['jwk'] = key.public_key() assert set(header_params).issubset(cls.header_cls._fields) assert protect.issubset(cls.header_cls._fields) protected_params = {} for header in protect: if header in header_params: protected_params[header] = header_params.pop(header) if protected_params: # pylint: disable=star-args protected = cls.header_cls(**protected_params).json_dumps() else: protected = '' header = cls.header_cls(**header_params) # pylint: disable=star-args signature = alg.sign(key.key, cls._msg(protected, payload)) return cls(protected=protected, header=header, signature=signature) def fields_to_partial_json(self) -> Dict[str, Any]: fields = super().fields_to_partial_json() if not fields['header'].not_omitted(): del fields['header'] return fields @classmethod def fields_from_json(cls, jobj: Mapping[str, Any]) -> Dict[str, Any]: fields = super().fields_from_json(jobj) fields_with_combined = cls._with_combined(fields) if 'alg' not in fields_with_combined['combined'].not_omitted(): raise errors.DeserializationError('alg not present') return fields_with_combined class JWS(json_util.JSONObjectWithFields): """JSON Web Signature. :ivar str payload: JWS Payload. :ivar str signature: JWS Signatures. """ __slots__ = ('payload', 'signatures') payload: bytes signatures: List[Signature] signature_cls = Signature def verify(self, key: Optional[josepy.JWK] = None) -> bool: """Verify.""" return all(sig.verify(self.payload, key) for sig in self.signatures) @classmethod def sign(cls, payload: bytes, **kwargs: Any) -> 'JWS': """Sign.""" return cls(payload=payload, signatures=( cls.signature_cls.sign(payload=payload, **kwargs),)) @property def signature(self) -> Signature: """Get a singleton signature. :rtype: :class:`JWS.signature_cls` """ assert len(self.signatures) == 1 return self.signatures[0] def to_compact(self) -> bytes: """Compact serialization. :rtype: bytes """ assert len(self.signatures) == 1 assert 'alg' not in self.signature.header.not_omitted() # ... it must be in protected return ( b64.b64encode(self.signature.protected.encode('utf-8')) + b'.' + b64.b64encode(self.payload) + b'.' + b64.b64encode(self.signature.signature)) @classmethod def from_compact(cls, compact: bytes) -> 'JWS': """Compact deserialization. :param bytes compact: """ try: protected, payload, signature = compact.split(b'.') except ValueError: raise errors.DeserializationError( 'Compact JWS serialization should comprise of exactly' ' 3 dot-separated components') sig = cls.signature_cls( protected=b64.b64decode(protected).decode('utf-8'), signature=b64.b64decode(signature)) return cls(payload=b64.b64decode(payload), signatures=(sig,)) def to_partial_json(self, flat: bool = True) -> Dict[str, Any]: # pylint: disable=arguments-differ assert self.signatures payload = json_util.encode_b64jose(self.payload) if flat and len(self.signatures) == 1: ret = self.signatures[0].to_partial_json() ret['payload'] = payload return ret else: return { 'payload': payload, 'signatures': self.signatures, } @classmethod def from_json(cls, jobj: Dict[str, Any]) -> 'JWS': if 'signature' in jobj and 'signatures' in jobj: raise errors.DeserializationError('Flat mixed with non-flat') elif 'signature' in jobj: # flat return cls(payload=json_util.decode_b64jose(jobj.pop('payload')), signatures=(cls.signature_cls.from_json(jobj),)) else: return cls(payload=json_util.decode_b64jose(jobj['payload']), signatures=tuple(cls.signature_cls.from_json(sig) for sig in jobj['signatures'])) class CLI: """JWS CLI.""" @classmethod def sign(cls, args: argparse.Namespace) -> None: """Sign.""" key = args.alg.kty.load(args.key.read()) args.key.close() if args.protect is None: args.protect = [] if args.compact: args.protect.append('alg') sig = JWS.sign(payload=sys.stdin.read().encode(), key=key, alg=args.alg, protect=set(args.protect)) if args.compact: print(sig.to_compact().decode('utf-8')) else: # JSON print(sig.json_dumps_pretty()) @classmethod def verify(cls, args: argparse.Namespace) -> bool: """Verify.""" if args.compact: sig = JWS.from_compact(sys.stdin.read().encode()) else: # JSON try: sig = cast(JWS, JWS.json_loads(sys.stdin.read())) except errors.Error as error: print(error) return False if args.key is not None: assert args.kty is not None key = args.kty.load(args.key.read()).public_key() args.key.close() else: key = None sys.stdout.write(sig.payload.decode()) return not sig.verify(key=key) @classmethod def _alg_type(cls, arg: Any) -> jwa.JWASignature: return jwa.JWASignature.from_json(arg) @classmethod def _header_type(cls, arg: Any) -> Any: assert arg in Signature.header_cls._fields return arg @classmethod def _kty_type(cls, arg: Any) -> Type[jwk_mod.JWK]: assert arg in jwk_mod.JWK.TYPES return jwk_mod.JWK.TYPES[arg] @classmethod def run(cls, args: List[str] = None) -> Optional[bool]: """Parse arguments and sign/verify.""" if args is None: args = sys.argv[1:] parser = argparse.ArgumentParser() parser.add_argument('--compact', action='store_true') subparsers = parser.add_subparsers() parser_sign = subparsers.add_parser('sign') parser_sign.set_defaults(func=cls.sign) parser_sign.add_argument( '-k', '--key', type=argparse.FileType('rb'), required=True) parser_sign.add_argument( '-a', '--alg', type=cls._alg_type, default=jwa.RS256) parser_sign.add_argument( '-p', '--protect', action='append', type=cls._header_type) parser_verify = subparsers.add_parser('verify') parser_verify.set_defaults(func=cls.verify) parser_verify.add_argument( '-k', '--key', type=argparse.FileType('rb'), required=False) parser_verify.add_argument( '--kty', type=cls._kty_type, required=False) parsed = parser.parse_args(args) return parsed.func(parsed) if __name__ == '__main__': exit(CLI.run()) # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/magic_typing.py0000644000076500000240000000106400000000000016700 0ustar00bmwstaff"""Shim class to not have to depend on typing module in prod.""" # mypy: ignore-errors import sys import warnings from typing import Any warnings.warn("josepy.magic_typing is deprecated and will be removed in a future release.", DeprecationWarning) class TypingClass: """Ignore import errors by getting anything""" def __getattr__(self, name: str) -> Any: return None try: # mypy doesn't respect modifying sys.modules from typing import * # noqa: F401,F403 except ImportError: sys.modules[__name__] = TypingClass() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/py.typed0000644000076500000240000000000000000000000015340 0ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/src/josepy/util.py0000644000076500000240000002403100000000000015202 0ustar00bmwstaff"""JOSE utilities.""" import abc from types import ModuleType from typing import Union, Any, Callable, Iterator, Tuple, List, cast from collections.abc import Hashable, Mapping import sys import warnings from OpenSSL import crypto from cryptography.hazmat.primitives.asymmetric import ec, rsa # Deprecated. Please use built-in decorators @classmethod and abc.abstractmethod together instead. def abstractclassmethod(func: Callable) -> classmethod: return classmethod(abc.abstractmethod(func)) class ComparableX509: # pylint: disable=too-few-public-methods """Wrapper for OpenSSL.crypto.X509** objects that supports __eq__. :ivar wrapped: Wrapped certificate or certificate request. :type wrapped: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`. """ def __init__(self, wrapped: Union[crypto.X509, crypto.X509Req]) -> None: assert isinstance(wrapped, crypto.X509) or isinstance( wrapped, crypto.X509Req) self.wrapped = wrapped def __getattr__(self, name: str) -> Any: return getattr(self.wrapped, name) def _dump(self, filetype: int = crypto.FILETYPE_ASN1) -> bytes: """Dumps the object into a buffer with the specified encoding. :param int filetype: The desired encoding. Should be one of `OpenSSL.crypto.FILETYPE_ASN1`, `OpenSSL.crypto.FILETYPE_PEM`, or `OpenSSL.crypto.FILETYPE_TEXT`. :returns: Encoded X509 object. :rtype: bytes """ if isinstance(self.wrapped, crypto.X509): return crypto.dump_certificate(filetype, self.wrapped) # assert in __init__ makes sure this is X509Req return crypto.dump_certificate_request(filetype, self.wrapped) def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return NotImplemented # pylint: disable=protected-access return self._dump() == other._dump() def __hash__(self) -> int: return hash((self.__class__, self._dump())) def __repr__(self) -> str: return '<{0}({1!r})>'.format(self.__class__.__name__, self.wrapped) class ComparableKey: # pylint: disable=too-few-public-methods """Comparable wrapper for ``cryptography`` keys. See https://github.com/pyca/cryptography/issues/2122. """ __hash__: Callable[[], int] = NotImplemented def __init__(self, wrapped: Union[ rsa.RSAPrivateKeyWithSerialization, rsa.RSAPublicKeyWithSerialization, ec.EllipticCurvePrivateKeyWithSerialization, ec.EllipticCurvePublicKeyWithSerialization]): self._wrapped = wrapped def __getattr__(self, name: str) -> Any: return getattr(self._wrapped, name) def __eq__(self, other: Any) -> bool: # pylint: disable=protected-access if (not isinstance(other, self.__class__) or self._wrapped.__class__ is not other._wrapped.__class__): return NotImplemented elif hasattr(self._wrapped, 'private_numbers'): return self.private_numbers() == other.private_numbers() elif hasattr(self._wrapped, 'public_numbers'): return self.public_numbers() == other.public_numbers() else: return NotImplemented def __repr__(self) -> str: return '<{0}({1!r})>'.format(self.__class__.__name__, self._wrapped) def public_key(self) -> 'ComparableKey': """Get wrapped public key.""" if isinstance(self._wrapped, (rsa.RSAPublicKeyWithSerialization, ec.EllipticCurvePublicKeyWithSerialization)): return self return self.__class__(self._wrapped.public_key()) class ComparableRSAKey(ComparableKey): # pylint: disable=too-few-public-methods """Wrapper for ``cryptography`` RSA keys. Wraps around: - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` - :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` """ def __hash__(self) -> int: # public_numbers() hasn't got stable hash! # https://github.com/pyca/cryptography/issues/2143 if isinstance(self._wrapped, rsa.RSAPrivateKeyWithSerialization): priv = self.private_numbers() pub = priv.public_numbers return hash((self.__class__, priv.p, priv.q, priv.dmp1, priv.dmq1, priv.iqmp, pub.n, pub.e)) elif isinstance(self._wrapped, rsa.RSAPublicKeyWithSerialization): pub = self.public_numbers() return hash((self.__class__, pub.n, pub.e)) raise NotImplementedError() class ComparableECKey(ComparableKey): # pylint: disable=too-few-public-methods """Wrapper for ``cryptography`` RSA keys. Wraps around: - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` - :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` """ def __hash__(self) -> int: # public_numbers() hasn't got stable hash! # https://github.com/pyca/cryptography/issues/2143 if isinstance(self._wrapped, ec.EllipticCurvePrivateKeyWithSerialization): priv = self.private_numbers() pub = priv.public_numbers return hash((self.__class__, pub.curve.name, pub.x, pub.y, priv.private_value)) elif isinstance(self._wrapped, ec.EllipticCurvePublicKeyWithSerialization): pub = self.public_numbers() return hash((self.__class__, pub.curve.name, pub.x, pub.y)) raise NotImplementedError() class ImmutableMap(Mapping, Hashable): # pylint: disable=too-few-public-methods """Immutable key to value mapping with attribute access.""" __slots__: Tuple[str, ...] = () """Must be overridden in subclasses.""" def __init__(self, **kwargs: Any) -> None: if set(kwargs) != set(self.__slots__): raise TypeError( '__init__() takes exactly the following arguments: {0} ' '({1} given)'.format(', '.join(self.__slots__), ', '.join(kwargs) if kwargs else 'none')) for slot in self.__slots__: object.__setattr__(self, slot, kwargs.pop(slot)) def update(self, **kwargs: Any) -> 'ImmutableMap': """Return updated map.""" items = {**self, **kwargs} return type(self)(**items) # pylint: disable=star-args def __getitem__(self, key: str) -> Any: try: return getattr(self, key) except AttributeError: raise KeyError(key) def __iter__(self) -> Iterator[str]: return iter(self.__slots__) def __len__(self) -> int: return len(self.__slots__) def __hash__(self) -> int: return hash(tuple(getattr(self, slot) for slot in self.__slots__)) def __setattr__(self, name: str, value: Any) -> None: raise AttributeError("can't set attribute") def __repr__(self) -> str: return '{0}({1})'.format(self.__class__.__name__, ', '.join( '{0}={1!r}'.format(key, value) for key, value in self.items())) class frozendict(Mapping, Hashable): # pylint: disable=invalid-name,too-few-public-methods """Frozen dictionary.""" __slots__ = ('_items', '_keys') def __init__(self, *args: Any, **kwargs: Any) -> None: items: Mapping if kwargs and not args: items = dict(kwargs) elif len(args) == 1 and isinstance(args[0], Mapping): items = args[0] else: raise TypeError() # TODO: support generators/iterators object.__setattr__(self, '_items', items) object.__setattr__(self, '_keys', tuple(sorted(items.keys()))) def __getitem__(self, key: str) -> Any: return self._items[key] def __iter__(self) -> Iterator[str]: return iter(self._keys) def __len__(self) -> int: return len(self._items) def _sorted_items(self) -> Tuple[Tuple[str, Any], ...]: return tuple((key, self[key]) for key in self._keys) def __hash__(self) -> int: return hash(self._sorted_items()) def __getattr__(self, name: str) -> Any: try: return self._items[name] except KeyError: raise AttributeError(name) def __setattr__(self, name: str, value: Any) -> None: raise AttributeError("can't set attribute") def __repr__(self) -> str: return 'frozendict({0})'.format(', '.join('{0}={1!r}'.format( key, value) for key, value in self._sorted_items())) # This class takes a similar approach to the cryptography project to deprecate attributes # in public modules. See the _ModuleWithDeprecation class here: # https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 class _UtilDeprecationModule: """ Internal class delegating to a module, and displaying warnings when attributes related to the deprecated "abstractclassmethod" attributes in the josepy.util module. """ def __init__(self, module: ModuleType) -> None: self.__dict__['_module'] = module def __getattr__(self, attr: str) -> Any: if attr == 'abstractclassmethod': warnings.warn('The abstractclassmethod attribute in josepy.util is deprecated and will ' 'be removed soon. Please use the built-in decorators @classmethod and ' '@abc.abstractmethod together instead.', DeprecationWarning, stacklevel=2) return getattr(self._module, attr) def __setattr__(self, attr: str, value: Any) -> None: # pragma: no cover setattr(self._module, attr, value) def __delattr__(self, attr: str) -> None: # pragma: no cover delattr(self._module, attr) def __dir__(self) -> List[str]: # pragma: no cover return ['_module'] + dir(self._module) # Patching ourselves to warn about deprecation and planned removal of some elements in the module. sys.modules[__name__] = cast(ModuleType, _UtilDeprecationModule(sys.modules[__name__])) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2403204 josepy-1.10.0/src/josepy.egg-info/0000755000076500000240000000000000000000000015345 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/PKG-INFO0000644000076500000240000000270700000000000016450 0ustar00bmwstaffMetadata-Version: 2.1 Name: josepy Version: 1.10.0 Summary: JOSE protocol implementation in Python Home-page: https://github.com/certbot/josepy Author: Certbot Project Author-email: client-dev@letsencrypt.org License: Apache License 2.0 Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Security Requires-Python: >=3.6 Provides-Extra: dev Provides-Extra: docs Provides-Extra: tests License-File: LICENSE.txt JOSE protocol implementation in Python using cryptography .. image:: https://github.com/certbot/josepy/actions/workflows/check.yaml/badge.svg :target: https://github.com/certbot/josepy/actions/workflows/check.yaml .. image:: https://codecov.io/gh/certbot/josepy/branch/master/graph/badge.svg :target: https://codecov.io/gh/certbot/josepy .. image:: https://readthedocs.org/projects/josepy/badge/?version=latest :target: http://josepy.readthedocs.io/en/latest/?badge=latest Originally developed as part of the ACME_ protocol implementation. .. _ACME: https://pypi.python.org/pypi/acme ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/SOURCES.txt0000644000076500000240000000334400000000000017235 0ustar00bmwstaff.coveragerc CHANGELOG.rst CONTRIBUTING.md LICENSE.txt MANIFEST.in README.rst pytest.ini setup.cfg setup.py tox.ini docs/.gitignore docs/Makefile docs/changelog.rst docs/conf.py docs/index.rst docs/jws-help.txt docs/requirements.txt docs/_static/.gitignore docs/_templates/.gitignore docs/api/base64.rst docs/api/errors.rst docs/api/interfaces.rst docs/api/json_util.rst docs/api/jwa.rst docs/api/jwk.rst docs/api/jws.rst docs/api/util.rst docs/man/jws.rst src/josepy/__init__.py src/josepy/b64.py src/josepy/errors.py src/josepy/interfaces.py src/josepy/json_util.py src/josepy/jwa.py src/josepy/jwk.py src/josepy/jws.py src/josepy/magic_typing.py src/josepy/py.typed src/josepy/util.py src/josepy.egg-info/PKG-INFO src/josepy.egg-info/SOURCES.txt src/josepy.egg-info/dependency_links.txt src/josepy.egg-info/entry_points.txt src/josepy.egg-info/requires.txt src/josepy.egg-info/top_level.txt tests/b64_test.py tests/errors_test.py tests/interfaces_test.py tests/json_util_test.py tests/jwa_test.py tests/jwk_test.py tests/jws_test.py tests/magic_typing_test.py tests/test_util.py tests/util_test.py tests/testdata/README tests/testdata/cert-100sans.pem tests/testdata/cert-idnsans.pem tests/testdata/cert-san.pem tests/testdata/cert.der tests/testdata/cert.pem tests/testdata/critical-san.pem tests/testdata/csr-100sans.pem tests/testdata/csr-6sans.pem tests/testdata/csr-idnsans.pem tests/testdata/csr-nosans.pem tests/testdata/csr-san.pem tests/testdata/csr.der tests/testdata/csr.pem tests/testdata/dsa512_key.pem tests/testdata/ec_p256_key.pem tests/testdata/ec_p384_key.pem tests/testdata/ec_p521_key.pem tests/testdata/rsa1024_key.pem tests/testdata/rsa2048_cert.pem tests/testdata/rsa2048_key.pem tests/testdata/rsa256_key.pem tests/testdata/rsa512_key.pem././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/dependency_links.txt0000644000076500000240000000000100000000000021413 0ustar00bmwstaff ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/entry_points.txt0000644000076500000240000000005400000000000020642 0ustar00bmwstaff[console_scripts] jws = josepy.jws:CLI.run ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/requires.txt0000644000076500000240000000027000000000000017744 0ustar00bmwstaffcryptography>=0.8 PyOpenSSL>=0.13 setuptools>=1.0 [dev] pytest tox [docs] Sphinx>=1.0 sphinx_rtd_theme [tests] coverage>=4.0 flake8 mypy pytest-cov pytest-flake8>=0.5 pytest>=2.8.0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778604.0 josepy-1.10.0/src/josepy.egg-info/top_level.txt0000644000076500000240000000000700000000000020074 0ustar00bmwstaffjosepy ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1632778604.2412643 josepy-1.10.0/tests/0000755000076500000240000000000000000000000012715 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/b64_test.py0000644000076500000240000000432000000000000014720 0ustar00bmwstaff"""Tests for josepy.b64.""" import unittest # https://en.wikipedia.org/wiki/Base64#Examples B64_PADDING_EXAMPLES = { b'any carnal pleasure.': (b'YW55IGNhcm5hbCBwbGVhc3VyZS4', b'='), b'any carnal pleasure': (b'YW55IGNhcm5hbCBwbGVhc3VyZQ', b'=='), b'any carnal pleasur': (b'YW55IGNhcm5hbCBwbGVhc3Vy', b''), b'any carnal pleasu': (b'YW55IGNhcm5hbCBwbGVhc3U', b'='), b'any carnal pleas': (b'YW55IGNhcm5hbCBwbGVhcw', b'=='), } B64_URL_UNSAFE_EXAMPLES = { bytes((251, 239)): b'--8', bytes((255,)) * 2: b'__8', } class B64EncodeTest(unittest.TestCase): """Tests for josepy.b64.b64encode.""" @classmethod def _call(cls, data): from josepy.b64 import b64encode return b64encode(data) def test_empty(self): self.assertEqual(self._call(b''), b'') def test_unsafe_url(self): for text, b64 in B64_URL_UNSAFE_EXAMPLES.items(): self.assertEqual(self._call(text), b64) def test_different_paddings(self): for text, (b64, _) in B64_PADDING_EXAMPLES.items(): self.assertEqual(self._call(text), b64) def test_unicode_fails_with_type_error(self): self.assertRaises(TypeError, self._call, u'some unicode') class B64DecodeTest(unittest.TestCase): """Tests for josepy.b64.b64decode.""" @classmethod def _call(cls, data): from josepy.b64 import b64decode return b64decode(data) def test_unsafe_url(self): for text, b64 in B64_URL_UNSAFE_EXAMPLES.items(): self.assertEqual(self._call(b64), text) def test_input_without_padding(self): for text, (b64, _) in B64_PADDING_EXAMPLES.items(): self.assertEqual(self._call(b64), text) def test_input_with_padding(self): for text, (b64, pad) in B64_PADDING_EXAMPLES.items(): self.assertEqual(self._call(b64 + pad), text) def test_unicode_with_ascii(self): self.assertEqual(self._call(u'YQ'), b'a') def test_non_ascii_unicode_fails(self): self.assertRaises(ValueError, self._call, u'\u0105') def test_type_error_no_unicode_or_bytes(self): self.assertRaises(TypeError, self._call, object()) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/errors_test.py0000644000076500000240000000071700000000000015647 0ustar00bmwstaff"""Tests for josepy.errors.""" import unittest class UnrecognizedTypeErrorTest(unittest.TestCase): def setUp(self): from josepy.errors import UnrecognizedTypeError self.error = UnrecognizedTypeError('foo', {'type': 'foo'}) def test_str(self): self.assertEqual( "foo was not recognized, full message: {'type': 'foo'}", str(self.error)) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/interfaces_test.py0000644000076500000240000000701200000000000016451 0ustar00bmwstaff"""Tests for josepy.interfaces.""" import unittest class JSONDeSerializableTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes def setUp(self): from josepy.interfaces import JSONDeSerializable # pylint: disable=missing-docstring,invalid-name class Basic(JSONDeSerializable): def __init__(self, v): self.v = v def to_partial_json(self): return self.v @classmethod def from_json(cls, jobj): return cls(jobj) class Sequence(JSONDeSerializable): def __init__(self, x, y): self.x = x self.y = y def to_partial_json(self): return [self.x, self.y] @classmethod def from_json(cls, jobj): return cls( Basic.from_json(jobj[0]), Basic.from_json(jobj[1])) class Mapping(JSONDeSerializable): def __init__(self, x, y): self.x = x self.y = y def to_partial_json(self): return {self.x: self.y} @classmethod def from_json(cls, jobj): pass # pragma: no cover self.basic1 = Basic('foo1') self.basic2 = Basic('foo2') self.seq = Sequence(self.basic1, self.basic2) self.mapping = Mapping(self.basic1, self.basic2) self.nested = Basic([[self.basic1]]) self.tuple = Basic(('foo',)) # pylint: disable=invalid-name self.Basic = Basic self.Sequence = Sequence self.Mapping = Mapping def test_to_json_sequence(self): self.assertEqual(self.seq.to_json(), ['foo1', 'foo2']) def test_to_json_mapping(self): self.assertEqual(self.mapping.to_json(), {'foo1': 'foo2'}) def test_to_json_other(self): mock_value = object() self.assertIs(self.Basic(mock_value).to_json(), mock_value) def test_to_json_nested(self): self.assertEqual(self.nested.to_json(), [['foo1']]) def test_to_json(self): self.assertEqual(self.tuple.to_json(), (('foo', ))) def test_from_json_not_implemented(self): from josepy.interfaces import JSONDeSerializable self.assertRaises(TypeError, JSONDeSerializable.from_json, 'xxx') def test_json_loads(self): seq = self.Sequence.json_loads('["foo1", "foo2"]') self.assertIsInstance(seq, self.Sequence) self.assertIsInstance(seq.x, self.Basic) self.assertIsInstance(seq.y, self.Basic) self.assertEqual(seq.x.v, 'foo1') self.assertEqual(seq.y.v, 'foo2') def test_json_dumps(self): self.assertEqual('["foo1", "foo2"]', self.seq.json_dumps()) def test_json_dumps_pretty(self): self.assertEqual(self.seq.json_dumps_pretty(), '[\n "foo1",\n "foo2"\n]') def test_json_dump_default(self): from josepy.interfaces import JSONDeSerializable self.assertEqual( 'foo1', JSONDeSerializable.json_dump_default(self.basic1)) jobj = JSONDeSerializable.json_dump_default(self.seq) self.assertEqual(len(jobj), 2) self.assertIs(jobj[0], self.basic1) self.assertIs(jobj[1], self.basic2) def test_json_dump_default_type_error(self): from josepy.interfaces import JSONDeSerializable self.assertRaises( TypeError, JSONDeSerializable.json_dump_default, object()) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/json_util_test.py0000644000076500000240000003505600000000000016345 0ustar00bmwstaff"""Tests for josepy.json_util.""" import itertools import unittest from unittest import mock from josepy import errors, interfaces, util import test_util CERT = test_util.load_comparable_cert('cert.pem') CSR = test_util.load_comparable_csr('csr.pem') class FieldTest(unittest.TestCase): """Tests for josepy.json_util.field and josepy.json_util.Field.""" def test_field_function(self): from josepy.json_util import field, Field test = field("foo", default="bar") self.assertIsInstance(test, Field) self.assertEqual(test.json_name, "foo") self.assertEqual(test.default, "bar") def test_type_field_control(self): from josepy.json_util import field, JSONObjectWithFields class DummyProperlyTyped(JSONObjectWithFields): type: str = field('type') index: int = field('index') with self.assertRaises(ValueError): class DummyImproperlyTyped(JSONObjectWithFields): type = field('type') index: int = field('index') def test_no_omit_boolean(self): from josepy.json_util import Field for default, omitempty, value in itertools.product( [True, False], [True, False], [True, False]): self.assertIs( Field("foo", default=default, omitempty=omitempty).omit(value), False) def test_descriptors(self): mock_value = mock.MagicMock() # pylint: disable=missing-docstring def decoder(unused_value): return 'd' def encoder(unused_value): return 'e' from josepy.json_util import Field field = Field('foo') field = field.encoder(encoder) self.assertEqual('e', field.encode(mock_value)) field = field.decoder(decoder) self.assertEqual('e', field.encode(mock_value)) self.assertEqual('d', field.decode(mock_value)) def test_default_encoder_is_partial(self): class MockField(interfaces.JSONDeSerializable): # pylint: disable=missing-docstring def to_partial_json(self): return 'foo' # pragma: no cover @classmethod def from_json(cls, jobj): pass # pragma: no cover mock_field = MockField() from josepy.json_util import Field self.assertIs(Field.default_encoder(mock_field), mock_field) # in particular... self.assertNotEqual('foo', Field.default_encoder(mock_field)) def test_default_encoder_passthrough(self): mock_value = mock.MagicMock() from josepy.json_util import Field self.assertIs(Field.default_encoder(mock_value), mock_value) def test_default_decoder_list_to_tuple(self): from josepy.json_util import Field self.assertEqual((1, 2, 3), Field.default_decoder([1, 2, 3])) def test_default_decoder_dict_to_frozendict(self): from josepy.json_util import Field obj = Field.default_decoder({'x': 2}) self.assertIsInstance(obj, util.frozendict) self.assertEqual(obj, util.frozendict(x=2)) def test_default_decoder_passthrough(self): mock_value = mock.MagicMock() from josepy.json_util import Field self.assertIs(Field.default_decoder(mock_value), mock_value) class JSONObjectWithFieldsMetaTest(unittest.TestCase): """Tests for josepy.json_util.JSONObjectWithFieldsMeta.""" def setUp(self): from josepy.json_util import Field from josepy.json_util import JSONObjectWithFieldsMeta self.field = Field('Baz') self.field2 = Field('Baz2') # pylint: disable=invalid-name,missing-docstring,too-few-public-methods # pylint: disable=blacklisted-name class A(object, metaclass=JSONObjectWithFieldsMeta): __slots__ = ('bar',) baz = self.field class B(A): pass class C(A): baz = self.field2 self.a_cls = A self.b_cls = B self.c_cls = C def test_fields(self): # pylint: disable=protected-access,no-member self.assertEqual({'baz': self.field}, self.a_cls._fields) self.assertEqual({'baz': self.field}, self.b_cls._fields) def test_fields_inheritance(self): # pylint: disable=protected-access,no-member self.assertEqual({'baz': self.field2}, self.c_cls._fields) def test_slots(self): self.assertEqual(('bar', 'baz'), self.a_cls.__slots__) self.assertEqual(('baz',), self.b_cls.__slots__) def test_orig_slots(self): # pylint: disable=protected-access,no-member self.assertEqual(('bar',), self.a_cls._orig_slots) self.assertEqual((), self.b_cls._orig_slots) class JSONObjectWithFieldsTest(unittest.TestCase): """Tests for josepy.json_util.JSONObjectWithFields.""" # pylint: disable=protected-access def setUp(self): from josepy.json_util import JSONObjectWithFields from josepy.json_util import Field class MockJSONObjectWithFields(JSONObjectWithFields): # pylint: disable=invalid-name,missing-docstring,no-self-argument # pylint: disable=too-few-public-methods x = Field('x', omitempty=True, encoder=(lambda x: x * 2), decoder=(lambda x: x / 2)) y = Field('y') z = Field('Z') # on purpose uppercase @y.encoder def y(value): if value == 500: raise errors.SerializationError() return value @y.decoder def y(value): if value == 500: raise errors.DeserializationError() return value # pylint: disable=invalid-name self.MockJSONObjectWithFields = MockJSONObjectWithFields self.mock = MockJSONObjectWithFields(x=None, y=2, z=3) def test_init_defaults(self): self.assertEqual(self.mock, self.MockJSONObjectWithFields(y=2, z=3)) def test_encode(self): self.assertEqual(10, self.MockJSONObjectWithFields( x=5, y=0, z=0).encode("x")) def test_encode_wrong_field(self): self.assertRaises(errors.Error, self.mock.encode, 'foo') def test_encode_serialization_error_passthrough(self): self.assertRaises( errors.SerializationError, self.MockJSONObjectWithFields(y=500, z=None).encode, "y") def test_fields_to_partial_json_omits_empty(self): self.assertEqual(self.mock.fields_to_partial_json(), {'y': 2, 'Z': 3}) def test_fields_from_json_fills_default_for_empty(self): self.assertEqual( {'x': None, 'y': 2, 'z': 3}, self.MockJSONObjectWithFields.fields_from_json({'y': 2, 'Z': 3})) def test_fields_from_json_fails_on_missing(self): self.assertRaises( errors.DeserializationError, self.MockJSONObjectWithFields.fields_from_json, {'y': 0}) self.assertRaises( errors.DeserializationError, self.MockJSONObjectWithFields.fields_from_json, {'Z': 0}) self.assertRaises( errors.DeserializationError, self.MockJSONObjectWithFields.fields_from_json, {'x': 0, 'y': 0}) self.assertRaises( errors.DeserializationError, self.MockJSONObjectWithFields.fields_from_json, {'x': 0, 'Z': 0}) def test_fields_to_partial_json_encoder(self): self.assertEqual( self.MockJSONObjectWithFields(x=1, y=2, z=3).to_partial_json(), {'x': 2, 'y': 2, 'Z': 3}) def test_fields_from_json_decoder(self): self.assertEqual( {'x': 2, 'y': 2, 'z': 3}, self.MockJSONObjectWithFields.fields_from_json( {'x': 4, 'y': 2, 'Z': 3})) def test_fields_to_partial_json_error_passthrough(self): self.assertRaises( errors.SerializationError, self.MockJSONObjectWithFields( x=1, y=500, z=3).to_partial_json) def test_fields_from_json_error_passthrough(self): self.assertRaises( errors.DeserializationError, self.MockJSONObjectWithFields.from_json, {'x': 4, 'y': 500, 'Z': 3}) class DeEncodersTest(unittest.TestCase): def setUp(self): self.b64_cert = ( u'MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhM' u'CVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKz' u'ApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxF' u'DASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIx' u'ODIyMzQ0NVowdzELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRI' u'wEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTW' u'ljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4YW1wbGUuY29tMFwwD' u'QYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR7R_drnBSQ_zfx1vQLHUbFLh1' u'AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c-pVE6K-EdE_twuUCAwE' u'AATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksllvr6zJepBH5fMnd' u'fk3XJp10jT6VE-14KNtjh02a56GoraAvJAT5_H67E8GvJ_ocNnB_o' ) self.b64_csr = ( u'MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2F' u'uMRIwEAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECw' u'wWVW5pdmVyc2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb' u'20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD_N_HW9As' u'dRsUuHUBBBDlHwNlRd3fp580rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3' u'C5QIDAQABoCkwJwYJKoZIhvcNAQkOMRowGDAWBgNVHREEDzANggtleGFtcG' u'xlLmNvbTANBgkqhkiG9w0BAQsFAANBAHJH_O6BtC9aGzEVCMGOZ7z9iIRHW' u'Szr9x_bOzn7hLwsbXPAgO1QxEwL-X-4g20Gn9XBE1N9W6HCIEut2d8wACg' ) def test_encode_b64jose(self): from josepy.json_util import encode_b64jose encoded = encode_b64jose(b'x') self.assertIsInstance(encoded, str) self.assertEqual(u'eA', encoded) def test_decode_b64jose(self): from josepy.json_util import decode_b64jose decoded = decode_b64jose(u'eA') self.assertIsInstance(decoded, bytes) self.assertEqual(b'x', decoded) def test_decode_b64jose_padding_error(self): from josepy.json_util import decode_b64jose self.assertRaises(errors.DeserializationError, decode_b64jose, u'x') def test_decode_b64jose_size(self): from josepy.json_util import decode_b64jose self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=3)) self.assertRaises( errors.DeserializationError, decode_b64jose, u'Zm9v', size=2) self.assertRaises( errors.DeserializationError, decode_b64jose, u'Zm9v', size=4) def test_decode_b64jose_minimum_size(self): from josepy.json_util import decode_b64jose self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=3, minimum=True)) self.assertEqual(b'foo', decode_b64jose(u'Zm9v', size=2, minimum=True)) self.assertRaises(errors.DeserializationError, decode_b64jose, u'Zm9v', size=4, minimum=True) def test_encode_hex16(self): from josepy.json_util import encode_hex16 encoded = encode_hex16(b'foo') self.assertEqual(u'666f6f', encoded) self.assertIsInstance(encoded, str) def test_decode_hex16(self): from josepy.json_util import decode_hex16 decoded = decode_hex16(u'666f6f') self.assertEqual(b'foo', decoded) self.assertIsInstance(decoded, bytes) def test_decode_hex16_minimum_size(self): from josepy.json_util import decode_hex16 self.assertEqual(b'foo', decode_hex16(u'666f6f', size=3, minimum=True)) self.assertEqual(b'foo', decode_hex16(u'666f6f', size=2, minimum=True)) self.assertRaises(errors.DeserializationError, decode_hex16, u'666f6f', size=4, minimum=True) def test_decode_hex16_odd_length(self): from josepy.json_util import decode_hex16 self.assertRaises(errors.DeserializationError, decode_hex16, u'x') def test_encode_cert(self): from josepy.json_util import encode_cert self.assertEqual(self.b64_cert, encode_cert(CERT)) def test_decode_cert(self): from josepy.json_util import decode_cert cert = decode_cert(self.b64_cert) self.assertIsInstance(cert, util.ComparableX509) self.assertEqual(cert, CERT) self.assertRaises(errors.DeserializationError, decode_cert, u'') def test_encode_csr(self): from josepy.json_util import encode_csr self.assertEqual(self.b64_csr, encode_csr(CSR)) def test_decode_csr(self): from josepy.json_util import decode_csr csr = decode_csr(self.b64_csr) self.assertIsInstance(csr, util.ComparableX509) self.assertEqual(csr, CSR) self.assertRaises(errors.DeserializationError, decode_csr, u'') class TypedJSONObjectWithFieldsTest(unittest.TestCase): def setUp(self): from josepy.json_util import TypedJSONObjectWithFields # pylint: disable=missing-docstring,abstract-method # pylint: disable=too-few-public-methods class MockParentTypedJSONObjectWithFields(TypedJSONObjectWithFields): TYPES = {} type_field_name = 'type' @MockParentTypedJSONObjectWithFields.register class MockTypedJSONObjectWithFields( MockParentTypedJSONObjectWithFields): typ = 'test' __slots__ = ('foo',) @classmethod def fields_from_json(cls, jobj): return {'foo': jobj['foo']} def fields_to_partial_json(self): return {'foo': self.foo} self.parent_cls = MockParentTypedJSONObjectWithFields self.msg = MockTypedJSONObjectWithFields(foo='bar') def test_to_partial_json(self): self.assertEqual(self.msg.to_partial_json(), { 'type': 'test', 'foo': 'bar', }) def test_from_json_non_dict_fails(self): for value in [[], (), 5, "asd"]: # all possible input types self.assertRaises( errors.DeserializationError, self.parent_cls.from_json, value) def test_from_json_dict_no_type_fails(self): self.assertRaises( errors.DeserializationError, self.parent_cls.from_json, {}) def test_from_json_unknown_type_fails(self): self.assertRaises(errors.UnrecognizedTypeError, self.parent_cls.from_json, {'type': 'bar'}) def test_from_json_returns_obj(self): self.assertEqual({'foo': 'bar'}, self.parent_cls.from_json( {'type': 'test', 'foo': 'bar'})) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/jwa_test.py0000644000076500000240000002147500000000000015120 0ustar00bmwstaff"""Tests for josepy.jwa.""" import unittest from unittest import mock from josepy import errors import test_util RSA256_KEY = test_util.load_rsa_private_key('rsa256_key.pem') RSA512_KEY = test_util.load_rsa_private_key('rsa512_key.pem') RSA1024_KEY = test_util.load_rsa_private_key('rsa1024_key.pem') EC_P256_KEY = test_util.load_ec_private_key('ec_p256_key.pem') EC_P384_KEY = test_util.load_ec_private_key('ec_p384_key.pem') EC_P521_KEY = test_util.load_ec_private_key('ec_p521_key.pem') class JWASignatureTest(unittest.TestCase): """Tests for josepy.jwa.JWASignature.""" def setUp(self): from josepy.jwa import JWASignature class MockSig(JWASignature): # pylint: disable=missing-docstring,too-few-public-methods # pylint: disable=abstract-class-not-used def sign(self, key, msg): raise NotImplementedError() # pragma: no cover def verify(self, key, msg, sig): raise NotImplementedError() # pragma: no cover # pylint: disable=invalid-name self.Sig1 = MockSig('Sig1') self.Sig2 = MockSig('Sig2') def test_eq(self): self.assertEqual(self.Sig1, self.Sig1) def test_ne(self): self.assertNotEqual(self.Sig1, self.Sig2) def test_ne_other_type(self): self.assertNotEqual(self.Sig1, 5) def test_repr(self): self.assertEqual('Sig1', repr(self.Sig1)) self.assertEqual('Sig2', repr(self.Sig2)) def test_to_partial_json(self): self.assertEqual(self.Sig1.to_partial_json(), 'Sig1') self.assertEqual(self.Sig2.to_partial_json(), 'Sig2') def test_from_json(self): from josepy.jwa import JWASignature from josepy.jwa import RS256 self.assertIs(JWASignature.from_json('RS256'), RS256) class JWAHSTest(unittest.TestCase): # pylint: disable=too-few-public-methods def test_it(self): from josepy.jwa import HS256 sig = ( b"\xceR\xea\xcd\x94\xab\xcf\xfb\xe0\xacA.:\x1a'\x08i\xe2\xc4" b"\r\x85+\x0e\x85\xaeUZ\xd4\xb3\x97zO" ) self.assertEqual(HS256.sign(b'some key', b'foo'), sig) self.assertIs(HS256.verify(b'some key', b'foo', sig), True) self.assertIs(HS256.verify(b'some key', b'foo', sig + b'!'), False) class JWARSTest(unittest.TestCase): def test_sign_no_private_part(self): from josepy.jwa import RS256 self.assertRaises(errors.Error, RS256.sign, RSA512_KEY.public_key(), b'foo') def test_sign_key_too_small(self): from josepy.jwa import RS256 from josepy.jwa import PS256 self.assertRaises(errors.Error, RS256.sign, RSA256_KEY, b'foo') self.assertRaises(errors.Error, PS256.sign, RSA256_KEY, b'foo') def test_rs(self): from josepy.jwa import RS256 sig = ( b'|\xc6\xb2\xa4\xab(\x87\x99\xfa*:\xea\xf8\xa0N&}\x9f\x0f\xc0O' b'\xc6t\xa3\xe6\xfa\xbb"\x15Y\x80Y\xe0\x81\xb8\x88)\xba\x0c\x9c' b'\xa4\x99\x1e\x19&\xd8\xc7\x99S\x97\xfc\x85\x0cOV\xe6\x07\x99' b'\xd2\xb9.>}\xfd' ) self.assertEqual(RS256.sign(RSA512_KEY, b'foo'), sig) self.assertIs(RS256.verify(RSA512_KEY.public_key(), b'foo', sig), True) self.assertIs(RS256.verify( RSA512_KEY.public_key(), b'foo', sig + b'!'), False) def test_ps(self): from josepy.jwa import PS256 sig = PS256.sign(RSA1024_KEY, b'foo') self.assertIs(PS256.verify(RSA1024_KEY.public_key(), b'foo', sig), True) self.assertIs(PS256.verify( RSA1024_KEY.public_key(), b'foo', sig + b'!'), False) def test_sign_new_api(self): from josepy.jwa import RS256 key = mock.MagicMock() RS256.sign(key, "message") self.assertIs(key.sign.called, True) def test_sign_old_api(self): from josepy.jwa import RS256 key = mock.MagicMock(spec=[u'signer']) signer = mock.MagicMock() key.signer.return_value = signer RS256.sign(key, "message") self.assertIs(key.signer.called, True) self.assertIs(signer.update.called, True) self.assertIs(signer.finalize.called, True) def test_verify_new_api(self): from josepy.jwa import RS256 key = mock.MagicMock() RS256.verify(key, "message", "signature") self.assertIs(key.verify.called, True) def test_verify_old_api(self): from josepy.jwa import RS256 key = mock.MagicMock(spec=[u'verifier']) verifier = mock.MagicMock() key.verifier.return_value = verifier RS256.verify(key, "message", "signature") self.assertIs(key.verifier.called, True) self.assertIs(verifier.update.called, True) self.assertIs(verifier.verify.called, True) class JWAECTest(unittest.TestCase): def test_sign_no_private_part(self): from josepy.jwa import ES256 self.assertRaises( errors.Error, ES256.sign, EC_P256_KEY.public_key(), b'foo') def test_es256_sign_and_verify(self): from josepy.jwa import ES256 message = b'foo' signature = ES256.sign(EC_P256_KEY, message) self.assertIs(ES256.verify(EC_P256_KEY.public_key(), message, signature), True) def test_es384_sign_and_verify(self): from josepy.jwa import ES384 message = b'foo' signature = ES384.sign(EC_P384_KEY, message) self.assertIs(ES384.verify(EC_P384_KEY.public_key(), message, signature), True) def test_verify_with_wrong_jwa(self): from josepy.jwa import ES256, ES384 message = b'foo' signature = ES256.sign(EC_P256_KEY, message) self.assertIs(ES384.verify(EC_P384_KEY.public_key(), message, signature), False) def test_verify_with_different_key(self): from josepy.jwa import ES256 from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.backends import default_backend message = b'foo' signature = ES256.sign(EC_P256_KEY, message) different_key = ec.generate_private_key(ec.SECP256R1, default_backend()) self.assertIs(ES256.verify(different_key.public_key(), message, signature), False) def test_sign_new_api(self): from josepy.jwa import ES256 from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1 key = mock.MagicMock(curve=SECP256R1()) with mock.patch("josepy.jwa.decode_dss_signature") as decode_patch: decode_patch.return_value = (0, 0) ES256.sign(key, "message") self.assertIs(key.sign.called, True) def test_sign_old_api(self): from josepy.jwa import ES256 from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1 key = mock.MagicMock(spec=[u'signer'], curve=SECP256R1()) signer = mock.MagicMock() key.signer.return_value = signer with mock.patch("josepy.jwa.decode_dss_signature") as decode_patch: decode_patch.return_value = (0, 0) ES256.sign(key, "message") self.assertIs(key.signer.called, True) self.assertIs(signer.update.called, True) self.assertIs(signer.finalize.called, True) def test_verify_new_api(self): import math from josepy.jwa import ES256 from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1 key = mock.MagicMock(key_size=256, curve=SECP256R1()) ES256.verify(key, "message", b'\x00' * math.ceil(key.key_size / 8) * 2) self.assertIs(key.verify.called, True) def test_verify_old_api(self): import math from josepy.jwa import ES256 from cryptography.hazmat.primitives.asymmetric.ec import SECP521R1 key = mock.MagicMock(spec=[u'verifier'], key_size=521, curve=SECP521R1()) verifier = mock.MagicMock() key.verifier.return_value = verifier ES256.verify(key, "message", b'\x00' * math.ceil(key.key_size / 8) * 2) self.assertIs(key.verifier.called, True) self.assertIs(verifier.update.called, True) self.assertIs(verifier.verify.called, True) def test_signature_size(self): from josepy.jwa import ES512 from josepy.jwk import JWK key = JWK.from_json( { 'd': 'Af9KP6DqLRbtit6NS_LRIaCP_-NdC5l5R2ugbILdfpv6dS9R4wUPNxiGw-vVWumA56Yo1oBnEm8ZdR4W-u1lPHw5', 'x': 'AD4i4STyJ07iZJkHkpKEOuICpn6IHknzwAlrf-1w1a5dqOsRe30EECSN4vFxaeAmtdBSCKBwCq7h1q4bPgMrMUvF', 'y': 'AHAlXxrabjcx_yBxGObnm_DkEQMJK1E69OHY3x3VxF5VXoKc93CG4GLoaPvphZQvZnt5EfExQoPktwOMIVhBHaFR', 'crv': 'P-521', 'kty': 'EC' }) with mock.patch("josepy.jwa.decode_dss_signature") as decode_patch: decode_patch.return_value = (0, 0) sig = ES512.sign(key.key, b"test") self.assertEqual(len(sig), 2 * 66) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/jwk_test.py0000644000076500000240000003130500000000000015123 0ustar00bmwstaff"""Tests for josepy.jwk.""" import binascii import unittest from josepy import errors, json_util, util import test_util DSA_PEM = test_util.load_vector('dsa512_key.pem') RSA256_KEY = test_util.load_rsa_private_key('rsa256_key.pem') RSA512_KEY = test_util.load_rsa_private_key('rsa512_key.pem') EC_P256_KEY = test_util.load_ec_private_key('ec_p256_key.pem') EC_P384_KEY = test_util.load_ec_private_key('ec_p384_key.pem') EC_P521_KEY = test_util.load_ec_private_key('ec_p521_key.pem') class JWKTest(unittest.TestCase): """Tests for josepy.jwk.JWK.""" def test_load(self): from josepy.jwk import JWK self.assertRaises(errors.Error, JWK.load, DSA_PEM) def test_load_subclass_wrong_type(self): from josepy.jwk import JWKRSA self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM) class JWKTestBaseMixin: """Mixin test for JWK subclass tests.""" thumbprint: bytes = NotImplemented def test_thumbprint_private(self): self.assertEqual(self.thumbprint, self.jwk.thumbprint()) def test_thumbprint_public(self): self.assertEqual(self.thumbprint, self.jwk.public_key().thumbprint()) class JWKOctTest(unittest.TestCase, JWKTestBaseMixin): """Tests for josepy.jwk.JWKOct.""" thumbprint = (b"\xf3\xe7\xbe\xa8`\xd2\xdap\xe9}\x9c\xce>" b"\xd0\xfcI\xbe\xcd\x92'\xd4o\x0e\xf41\xea" b"\x8e(\x8a\xb2i\x1c") def setUp(self): from josepy.jwk import JWKOct self.jwk = JWKOct(key=b'foo') self.jobj = {'kty': 'oct', 'k': json_util.encode_b64jose(b'foo')} def test_to_partial_json(self): self.assertEqual(self.jwk.to_partial_json(), self.jobj) def test_from_json(self): from josepy.jwk import JWKOct self.assertEqual(self.jwk, JWKOct.from_json(self.jobj)) def test_from_json_hashable(self): from josepy.jwk import JWKOct hash(JWKOct.from_json(self.jobj)) def test_load(self): from josepy.jwk import JWKOct self.assertEqual(self.jwk, JWKOct.load(b'foo')) def test_public_key(self): self.assertIs(self.jwk.public_key(), self.jwk) class JWKRSATest(unittest.TestCase, JWKTestBaseMixin): """Tests for josepy.jwk.JWKRSA.""" # pylint: disable=too-many-instance-attributes thumbprint = (b'\x83K\xdc#3\x98\xca\x98\xed\xcb\x80\x80<\x0c' b'\xf0\x95\xb9H\xb2*l\xbd$\xe5&|O\x91\xd4 \xb0Y') def setUp(self): from josepy.jwk import JWKRSA self.jwk256 = JWKRSA(key=RSA256_KEY.public_key()) self.jwk256json = { 'kty': 'RSA', 'e': 'AQAB', 'n': 'm2Fylv-Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEk', } # pylint: disable=protected-access self.jwk256_not_comparable = JWKRSA( key=RSA256_KEY.public_key()._wrapped) self.jwk512 = JWKRSA(key=RSA512_KEY.public_key()) self.jwk512json = { 'kty': 'RSA', 'e': 'AQAB', 'n': 'rHVztFHtH92ucFJD_N_HW9AsdRsUuHUBBBDlHwNlRd3fp5' '80rv2-6QWE30cWgdmJS86ObRz6lUTor4R0T-3C5Q', } self.private = JWKRSA(key=RSA256_KEY) self.private_json_small = self.jwk256json.copy() self.private_json_small['d'] = ( 'lPQED_EPTV0UIBfNI3KP2d9Jlrc2mrMllmf946bu-CE') self.private_json = self.jwk256json.copy() self.private_json.update({ 'd': 'lPQED_EPTV0UIBfNI3KP2d9Jlrc2mrMllmf946bu-CE', 'p': 'zUVNZn4lLLBD1R6NE8TKNQ', 'q': 'wcfKfc7kl5jfqXArCRSURQ', 'dp': 'CWJFq43QvT5Bm5iN8n1okQ', 'dq': 'bHh2u7etM8LKKCF2pY2UdQ', 'qi': 'oi45cEkbVoJjAbnQpFY87Q', }) self.jwk = self.private def test_init_auto_comparable(self): self.assertIsInstance(self.jwk256_not_comparable.key, util.ComparableRSAKey) self.assertEqual(self.jwk256, self.jwk256_not_comparable) def test_encode_param_zero(self): from josepy.jwk import JWKRSA # pylint: disable=protected-access # TODO: move encode/decode _param to separate class self.assertEqual('AA', JWKRSA._encode_param(0)) def test_equals(self): self.assertEqual(self.jwk256, self.jwk256) self.assertEqual(self.jwk512, self.jwk512) def test_not_equals(self): self.assertNotEqual(self.jwk256, self.jwk512) self.assertNotEqual(self.jwk512, self.jwk256) def test_load(self): from josepy.jwk import JWKRSA self.assertEqual(self.private, JWKRSA.load( test_util.load_vector('rsa256_key.pem'))) def test_public_key(self): self.assertEqual(self.jwk256, self.private.public_key()) def test_to_partial_json(self): self.assertEqual(self.jwk256.to_partial_json(), self.jwk256json) self.assertEqual(self.jwk512.to_partial_json(), self.jwk512json) self.assertEqual(self.private.to_partial_json(), self.private_json) def test_from_json(self): from josepy.jwk import JWK self.assertEqual( self.jwk256, JWK.from_json(self.jwk256json)) self.assertEqual( self.jwk512, JWK.from_json(self.jwk512json)) self.assertEqual(self.private, JWK.from_json(self.private_json)) def test_from_json_private_small(self): from josepy.jwk import JWK self.assertEqual(self.private, JWK.from_json(self.private_json_small)) def test_from_json_missing_one_additional(self): from josepy.jwk import JWK del self.private_json['q'] self.assertRaises(errors.Error, JWK.from_json, self.private_json) def test_from_json_hashable(self): from josepy.jwk import JWK hash(JWK.from_json(self.jwk256json)) def test_from_json_non_schema_errors(self): # valid against schema, but still failing from josepy.jwk import JWK self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'RSA', 'e': 'AQAB', 'n': ''}) self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'RSA', 'e': 'AQAB', 'n': '1'}) def test_thumbprint_go_jose(self): # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk.go#L155 # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L331-L344 # https://github.com/square/go-jose/blob/4ddd71883fa547d37fbf598071f04512d8bafee3/jwk_test.go#L384 from josepy.jwk import JWKRSA key = JWKRSA.json_loads("""{ "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw", "e": "AQAB" }""") self.assertEqual( binascii.hexlify(key.thumbprint()), b"f63838e96077ad1fc01c3f8405774dedc0641f558ebb4b40dccf5f9b6d66a932") class JWKECTest(unittest.TestCase, JWKTestBaseMixin): """Tests for josepy.jwk.JWKEC.""" # pylint: disable=too-many-instance-attributes thumbprint = (b'\x06\xceL\x1b\xa8\x8d\x86\x1flF\x99J\x8b\xe0$\t\xbbj' b'\xd8\xf6O\x1ed\xdeR\x8f\x97\xff\xf6\xa2\x86\xd3') def setUp(self): from josepy.jwk import JWKEC self.jwk256 = JWKEC(key=EC_P256_KEY.public_key()) self.jwk384 = JWKEC(key=EC_P384_KEY.public_key()) self.jwk521 = JWKEC(key=EC_P521_KEY.public_key()) self.jwk256_not_comparable = JWKEC(key=EC_P256_KEY.public_key()._wrapped) self.jwk256json = { 'kty': 'EC', 'crv': 'P-256', 'x': 'jjQtV-fA7J_tK8dPzYq7jRPNjF8r5p6LW2R25S2Gw5U', 'y': 'EPAw8_8z7PYKsHH6hlGSlsWxFoFl7-0vM0QRGbmnvCc', } self.jwk384json = { 'kty': 'EC', 'crv': 'P-384', 'x': 'tIhpNtEXkadUbrY84rYGgApFM1X_3l3EWQRuOP1IWtxlTftrZQwneJZF0k0eRn00', 'y': 'KW2Gp-TThDXmZ-9MJPnD8hv-X130SVvfZRl1a04HPVwIbvLe87mvA_iuOa-myUyv', } self.jwk521json = { 'kty': 'EC', 'crv': 'P-521', 'x': 'AFkdl6cKzBmP18U8fffpP4IZN2eED45hDcwRPl5ZeClwHcLtnMBMuWYFFO_Nzm6DL2MhpN0zI2bcMLJd95aY2tPs', 'y': 'AYvZq3wByjt7nQd8nYMqhFNCL3j_-U6GPWZet1hYBY_XZHrC4yIV0R4JnssRAY9eqc1EElpCc4hziis1jiV1iR4W', } self.private = JWKEC(key=EC_P256_KEY) self.private_json = { 'd': 'xReNQBKqqTthG8oTmBdhp4EQYImSK1dVqfa2yyMn2rc', 'x': 'jjQtV-fA7J_tK8dPzYq7jRPNjF8r5p6LW2R25S2Gw5U', 'y': 'EPAw8_8z7PYKsHH6hlGSlsWxFoFl7-0vM0QRGbmnvCc', 'crv': 'P-256', 'kty': 'EC'} self.jwk = self.private def test_init_auto_comparable(self): self.assertIsInstance( self.jwk256_not_comparable.key, util.ComparableECKey) self.assertEqual(self.jwk256, self.jwk256_not_comparable) def test_encode_param_zero(self): from josepy.jwk import JWKEC # pylint: disable=protected-access # TODO: move encode/decode _param to separate class self.assertEqual('AA', JWKEC._encode_param(0, 1)) def test_equals(self): self.assertEqual(self.jwk256, self.jwk256) self.assertEqual(self.jwk384, self.jwk384) self.assertEqual(self.jwk521, self.jwk521) def test_not_equals(self): self.assertNotEqual(self.jwk256, self.jwk384) self.assertNotEqual(self.jwk256, self.jwk521) self.assertNotEqual(self.jwk384, self.jwk256) self.assertNotEqual(self.jwk384, self.jwk521) self.assertNotEqual(self.jwk521, self.jwk256) self.assertNotEqual(self.jwk521, self.jwk384) def test_load(self): from josepy.jwk import JWKEC self.assertEqual(self.private, JWKEC.load( test_util.load_vector('ec_p256_key.pem'))) def test_public_key(self): self.assertEqual(self.jwk256, self.private.public_key()) def test_to_partial_json(self): self.assertEqual(self.jwk256.to_partial_json(), self.jwk256json) self.assertEqual(self.jwk384.to_partial_json(), self.jwk384json) self.assertEqual(self.jwk521.to_partial_json(), self.jwk521json) self.assertEqual(self.private.to_partial_json(), self.private_json) def test_from_json(self): from josepy.jwk import JWK self.assertEqual( self.jwk256, JWK.from_json(self.jwk256json)) self.assertEqual( self.jwk384, JWK.from_json(self.jwk384json)) self.assertEqual( self.jwk521, JWK.from_json(self.jwk521json)) self.assertEqual( self.private, JWK.from_json(self.private_json)) def test_from_json_missing_x_coordinate(self): from josepy.jwk import JWK del self.private_json['x'] self.assertRaises(KeyError, JWK.from_json, self.private_json) def test_from_json_missing_y_coordinate(self): from josepy.jwk import JWK del self.private_json['y'] self.assertRaises(KeyError, JWK.from_json, self.private_json) def test_from_json_hashable(self): from josepy.jwk import JWK hash(JWK.from_json(self.jwk256json)) def test_from_json_non_schema_errors(self): # valid against schema, but still failing from josepy.jwk import JWK self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'EC', 'crv': 'P-256', 'x': 'AQAB', 'y': 'm2Fylv-Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEk'}) self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'EC', 'crv': 'P-256', 'x': 'jjQtV-fA7J_tK8dPzYq7jRPNjF8r5p6LW2R25S2Gw5U', 'y': '1'}) def test_unknown_crv_name(self): from josepy.jwk import JWK self.assertRaises(errors.DeserializationError, JWK.from_json, {'kty': 'EC', 'crv': 'P-255', 'x': 'jjQtV-fA7J_tK8dPzYq7jRPNjF8r5p6LW2R25S2Gw5U', 'y': 'EPAw8_8z7PYKsHH6hlGSlsWxFoFl7-0vM0QRGbmnvCc'}) def test_encode_y_leading_zero_p256(self): from josepy.jwk import JWKEC, JWK import josepy data = b"""-----BEGIN EC PRIVATE KEY----- MHcCAQEEICZ7LCI99Na2KZ/Fq8JmJROakGJ5+J7rHiGSPoO36kOAoAoGCCqGSM49 AwEHoUQDQgAEGS5RvStca15z2FEanCM3juoX7tE/LB7iD44GWawGE40APAl/iZuH 31wQfst4glTZpxkpEI/MzNZHjiYnqrGeSw== -----END EC PRIVATE KEY-----""" key = JWKEC.load(data) data = key.to_partial_json() y = josepy.json_util.decode_b64jose(data['y']) self.assertEqual(y[0], 0) self.assertEqual(len(y), 32) JWK.from_json(data) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/jws_test.py0000644000076500000240000002051700000000000015136 0ustar00bmwstaff"""Tests for josepy.jws.""" import base64 import unittest from unittest import mock import OpenSSL from josepy import errors, json_util, jwa, jwk import test_util CERT = test_util.load_comparable_cert('cert.pem') KEY = jwk.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) class MediaTypeTest(unittest.TestCase): """Tests for josepy.jws.MediaType.""" def test_decode(self): from josepy.jws import MediaType self.assertEqual('application/app', MediaType.decode('application/app')) self.assertEqual('application/app', MediaType.decode('app')) self.assertRaises( errors.DeserializationError, MediaType.decode, 'app;foo') def test_encode(self): from josepy.jws import MediaType self.assertEqual('app', MediaType.encode('application/app')) self.assertEqual('application/app;foo', MediaType.encode('application/app;foo')) class HeaderTest(unittest.TestCase): """Tests for josepy.jws.Header.""" def setUp(self): from josepy.jws import Header self.header1 = Header(jwk='foo') self.header2 = Header(jwk='bar') self.crit = Header(crit=('a', 'b')) self.empty = Header() def test_add_non_empty(self): from josepy.jws import Header self.assertEqual(Header(jwk='foo', crit=('a', 'b')), self.header1 + self.crit) def test_add_empty(self): self.assertEqual(self.header1, self.header1 + self.empty) self.assertEqual(self.header1, self.empty + self.header1) def test_add_overlapping_error(self): self.assertRaises(TypeError, self.header1.__add__, self.header2) def test_add_wrong_type_error(self): self.assertRaises(TypeError, self.header1.__add__, 'xxx') def test_crit_decode_always_errors(self): from josepy.jws import Header self.assertRaises(errors.DeserializationError, Header.from_json, {'crit': ['a', 'b']}) def test_x5c_decoding(self): from josepy.jws import Header header = Header(x5c=(CERT, CERT)) jobj = header.to_partial_json() cert_asn1 = OpenSSL.crypto.dump_certificate( OpenSSL.crypto.FILETYPE_ASN1, CERT.wrapped) cert_b64 = base64.b64encode(cert_asn1) self.assertEqual(jobj, {'x5c': [cert_b64, cert_b64]}) self.assertEqual(header, Header.from_json(jobj)) jobj['x5c'][0] = base64.b64encode(b'xxx' + cert_asn1) self.assertRaises(errors.DeserializationError, Header.from_json, jobj) def test_find_key(self): self.assertEqual('foo', self.header1.find_key()) self.assertEqual('bar', self.header2.find_key()) self.assertRaises(errors.Error, self.crit.find_key) class SignatureTest(unittest.TestCase): """Tests for josepy.jws.Signature.""" def test_from_json(self): from josepy.jws import Header from josepy.jws import Signature self.assertEqual( Signature(signature=b'foo', header=Header(alg=jwa.RS256)), Signature.from_json( {'signature': 'Zm9v', 'header': {'alg': 'RS256'}})) def test_from_json_no_alg_error(self): from josepy.jws import Signature self.assertRaises(errors.DeserializationError, Signature.from_json, {'signature': 'foo'}) class JWSTest(unittest.TestCase): """Tests for josepy.jws.JWS.""" def setUp(self): self.privkey = KEY self.pubkey = self.privkey.public_key() from josepy.jws import JWS self.unprotected = JWS.sign( payload=b'foo', key=self.privkey, alg=jwa.RS256) self.protected = JWS.sign( payload=b'foo', key=self.privkey, alg=jwa.RS256, protect=frozenset(['jwk', 'alg'])) self.mixed = JWS.sign( payload=b'foo', key=self.privkey, alg=jwa.RS256, protect=frozenset(['alg'])) def test_pubkey_jwk(self): self.assertEqual(self.unprotected.signature.combined.jwk, self.pubkey) self.assertEqual(self.protected.signature.combined.jwk, self.pubkey) self.assertEqual(self.mixed.signature.combined.jwk, self.pubkey) def test_sign_unprotected(self): self.assertIs(self.unprotected.verify(), True) def test_sign_protected(self): self.assertIs(self.protected.verify(), True) def test_sign_mixed(self): self.assertIs(self.mixed.verify(), True) def test_compact_lost_unprotected(self): compact = self.mixed.to_compact() self.assertEqual( b'eyJhbGciOiAiUlMyNTYifQ.Zm9v.OHdxFVj73l5LpxbFp1AmYX4yJM0Pyb' b'_893n1zQjpim_eLS5J1F61lkvrCrCDErTEJnBGOGesJ72M7b6Ve1cAJA', compact) from josepy.jws import JWS mixed = JWS.from_compact(compact) self.assertNotEqual(self.mixed, mixed) self.assertEqual({'alg'}, set(mixed.signature.combined.not_omitted())) def test_from_compact_missing_components(self): from josepy.jws import JWS self.assertRaises(errors.DeserializationError, JWS.from_compact, b'.') def test_json_omitempty(self): protected_jobj = self.protected.to_partial_json(flat=True) unprotected_jobj = self.unprotected.to_partial_json(flat=True) self.assertNotIn('protected', unprotected_jobj) self.assertNotIn('header', protected_jobj) unprotected_jobj['header'] = unprotected_jobj['header'].to_json() from josepy.jws import JWS self.assertEqual(JWS.from_json(protected_jobj), self.protected) self.assertEqual(JWS.from_json(unprotected_jobj), self.unprotected) def test_json_flat(self): jobj_to = { 'signature': json_util.encode_b64jose( self.mixed.signature.signature), 'payload': json_util.encode_b64jose(b'foo'), 'header': self.mixed.signature.header, 'protected': json_util.encode_b64jose( self.mixed.signature.protected.encode('utf-8')), } jobj_from = jobj_to.copy() jobj_from['header'] = jobj_from['header'].to_json() self.assertEqual(self.mixed.to_partial_json(flat=True), jobj_to) from josepy.jws import JWS self.assertEqual(self.mixed, JWS.from_json(jobj_from)) def test_json_not_flat(self): jobj_to = { 'signatures': (self.mixed.signature,), 'payload': json_util.encode_b64jose(b'foo'), } jobj_from = jobj_to.copy() jobj_from['signatures'] = [jobj_to['signatures'][0].to_json()] self.assertEqual(self.mixed.to_partial_json(flat=False), jobj_to) from josepy.jws import JWS self.assertEqual(self.mixed, JWS.from_json(jobj_from)) def test_from_json_mixed_flat(self): from josepy.jws import JWS self.assertRaises(errors.DeserializationError, JWS.from_json, {'signatures': (), 'signature': 'foo'}) def test_from_json_hashable(self): from josepy.jws import JWS hash(JWS.from_json(self.mixed.to_json())) class CLITest(unittest.TestCase): def setUp(self): self.key_path = test_util.vector_path('rsa512_key.pem') def test_unverified(self): from josepy.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = '{"payload": "foo", "signature": "xxx"}' with mock.patch('sys.stdout'): self.assertEqual(False, CLI.run(['verify'])) def test_json(self): from josepy.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' with mock.patch('sys.stdout') as sout: CLI.run(['sign', '-k', self.key_path, '-a', 'RS256', '-p', 'jwk']) sin.read.return_value = sout.write.mock_calls[0][1][0] self.assertEqual(0, CLI.run(['verify'])) def test_compact(self): from josepy.jws import CLI with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' with mock.patch('sys.stdout') as sout: CLI.run(['--compact', 'sign', '-k', self.key_path]) sin.read.return_value = sout.write.mock_calls[0][1][0] self.assertEqual(0, CLI.run([ '--compact', 'verify', '--kty', 'RSA', '-k', self.key_path])) if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/magic_typing_test.py0000644000076500000240000000206500000000000017003 0ustar00bmwstaff"""Tests for josepy.magic_typing.""" import sys import unittest import warnings from unittest import mock class MagicTypingTest(unittest.TestCase): """Tests for josepy.magic_typing.""" def test_import_success(self): try: import typing as temp_typing except ImportError: # pragma: no cover temp_typing = None # pragma: no cover typing_class_mock = mock.MagicMock() text_mock = mock.MagicMock() typing_class_mock.Text = text_mock sys.modules['typing'] = typing_class_mock if 'josepy.magic_typing' in sys.modules: del sys.modules['josepy.magic_typing'] # pragma: no cover with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning) from josepy.magic_typing import Text # pylint: disable=no-name-in-module self.assertEqual(Text, text_mock) del sys.modules['josepy.magic_typing'] sys.modules['typing'] = temp_typing if __name__ == '__main__': unittest.main() # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/test_util.py0000644000076500000240000000537100000000000015311 0ustar00bmwstaff"""Test utilities.""" import os from typing import Any import josepy.util from OpenSSL import crypto import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization from josepy import ComparableRSAKey, ComparableX509 from josepy.util import ComparableECKey def vector_path(*names: str) -> str: """Path to a test vector.""" return pkg_resources.resource_filename( __name__, os.path.join('testdata', *names)) def load_vector(*names: str) -> bytes: """Load contents of a test vector.""" # luckily, resource_string opens file in binary mode return pkg_resources.resource_string( __name__, os.path.join('testdata', *names)) def _guess_loader(filename: str, loader_pem: Any, loader_der: Any) -> Any: _, ext = os.path.splitext(filename) if ext.lower() == '.pem': return loader_pem elif ext.lower() == '.der': return loader_der else: # pragma: no cover raise ValueError("Loader could not be recognized based on extension") def load_cert(*names: str) -> crypto.X509: """Load certificate.""" loader = _guess_loader( names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) return crypto.load_certificate(loader, load_vector(*names)) def load_comparable_cert(*names: str) -> josepy.util.ComparableX509: """Load ComparableX509 cert.""" return ComparableX509(load_cert(*names)) def load_csr(*names: str) -> crypto.X509Req: """Load certificate request.""" loader = _guess_loader( names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) return crypto.load_certificate_request(loader, load_vector(*names)) def load_comparable_csr(*names: str) -> josepy.util.ComparableX509: """Load ComparableX509 certificate request.""" return ComparableX509(load_csr(*names)) def load_rsa_private_key(*names: str) -> josepy.util.ComparableRSAKey: """Load RSA private key.""" loader = _guess_loader(names[-1], serialization.load_pem_private_key, serialization.load_der_private_key) return ComparableRSAKey(loader( load_vector(*names), password=None, backend=default_backend())) def load_ec_private_key(*names: str) -> josepy.util.ComparableECKey: """Load EC private key.""" loader = _guess_loader(names[-1], serialization.load_pem_private_key, serialization.load_der_private_key) return ComparableECKey(loader( load_vector(*names), password=None, backend=default_backend())) def load_pyopenssl_private_key(*names: str) -> crypto.PKey: """Load pyOpenSSL private key.""" loader = _guess_loader( names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) return crypto.load_privatekey(loader, load_vector(*names)) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1632778604.243312 josepy-1.10.0/tests/testdata/0000755000076500000240000000000000000000000014526 5ustar00bmwstaff././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/README0000644000076500000240000000130700000000000015407 0ustar00bmwstaffIn order for josepy.test_util._guess_loader to work properly, make sure to use appropriate extension for vector filenames: .pem for PEM and .der for DER. The following command has been used to generate test keys: for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done openssl ecparam -name prime256v1 -genkey -out ec_p256_key.pem openssl ecparam -name secp384r1 -genkey -out ec_p384_key.pem openssl ecparam -name secp521r1 -genkey -out ec_p521_key.pem and for the CSR: openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der and for the certificate: openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/cert-100sans.pem0000644000076500000240000000530000000000000017347 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIIHxDCCB26gAwIBAgIJAOGrG1Un9lHiMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X DTE2MDEwNjE5MDkzN1oXDTE2MDEwNzE5MDkzN1owZDELMAkGA1UECAwCQ0ExFjAU BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IGATCCBf0wCQYDVR0T BAIwADALBgNVHQ8EBAMCBeAwggXhBgNVHREEggXYMIIF1IIMZXhhbXBsZTEuY29t ggxleGFtcGxlMi5jb22CDGV4YW1wbGUzLmNvbYIMZXhhbXBsZTQuY29tggxleGFt cGxlNS5jb22CDGV4YW1wbGU2LmNvbYIMZXhhbXBsZTcuY29tggxleGFtcGxlOC5j b22CDGV4YW1wbGU5LmNvbYINZXhhbXBsZTEwLmNvbYINZXhhbXBsZTExLmNvbYIN ZXhhbXBsZTEyLmNvbYINZXhhbXBsZTEzLmNvbYINZXhhbXBsZTE0LmNvbYINZXhh bXBsZTE1LmNvbYINZXhhbXBsZTE2LmNvbYINZXhhbXBsZTE3LmNvbYINZXhhbXBs ZTE4LmNvbYINZXhhbXBsZTE5LmNvbYINZXhhbXBsZTIwLmNvbYINZXhhbXBsZTIx LmNvbYINZXhhbXBsZTIyLmNvbYINZXhhbXBsZTIzLmNvbYINZXhhbXBsZTI0LmNv bYINZXhhbXBsZTI1LmNvbYINZXhhbXBsZTI2LmNvbYINZXhhbXBsZTI3LmNvbYIN ZXhhbXBsZTI4LmNvbYINZXhhbXBsZTI5LmNvbYINZXhhbXBsZTMwLmNvbYINZXhh bXBsZTMxLmNvbYINZXhhbXBsZTMyLmNvbYINZXhhbXBsZTMzLmNvbYINZXhhbXBs ZTM0LmNvbYINZXhhbXBsZTM1LmNvbYINZXhhbXBsZTM2LmNvbYINZXhhbXBsZTM3 LmNvbYINZXhhbXBsZTM4LmNvbYINZXhhbXBsZTM5LmNvbYINZXhhbXBsZTQwLmNv bYINZXhhbXBsZTQxLmNvbYINZXhhbXBsZTQyLmNvbYINZXhhbXBsZTQzLmNvbYIN ZXhhbXBsZTQ0LmNvbYINZXhhbXBsZTQ1LmNvbYINZXhhbXBsZTQ2LmNvbYINZXhh bXBsZTQ3LmNvbYINZXhhbXBsZTQ4LmNvbYINZXhhbXBsZTQ5LmNvbYINZXhhbXBs ZTUwLmNvbYINZXhhbXBsZTUxLmNvbYINZXhhbXBsZTUyLmNvbYINZXhhbXBsZTUz LmNvbYINZXhhbXBsZTU0LmNvbYINZXhhbXBsZTU1LmNvbYINZXhhbXBsZTU2LmNv bYINZXhhbXBsZTU3LmNvbYINZXhhbXBsZTU4LmNvbYINZXhhbXBsZTU5LmNvbYIN ZXhhbXBsZTYwLmNvbYINZXhhbXBsZTYxLmNvbYINZXhhbXBsZTYyLmNvbYINZXhh bXBsZTYzLmNvbYINZXhhbXBsZTY0LmNvbYINZXhhbXBsZTY1LmNvbYINZXhhbXBs ZTY2LmNvbYINZXhhbXBsZTY3LmNvbYINZXhhbXBsZTY4LmNvbYINZXhhbXBsZTY5 LmNvbYINZXhhbXBsZTcwLmNvbYINZXhhbXBsZTcxLmNvbYINZXhhbXBsZTcyLmNv bYINZXhhbXBsZTczLmNvbYINZXhhbXBsZTc0LmNvbYINZXhhbXBsZTc1LmNvbYIN ZXhhbXBsZTc2LmNvbYINZXhhbXBsZTc3LmNvbYINZXhhbXBsZTc4LmNvbYINZXhh bXBsZTc5LmNvbYINZXhhbXBsZTgwLmNvbYINZXhhbXBsZTgxLmNvbYINZXhhbXBs ZTgyLmNvbYINZXhhbXBsZTgzLmNvbYINZXhhbXBsZTg0LmNvbYINZXhhbXBsZTg1 LmNvbYINZXhhbXBsZTg2LmNvbYINZXhhbXBsZTg3LmNvbYINZXhhbXBsZTg4LmNv bYINZXhhbXBsZTg5LmNvbYINZXhhbXBsZTkwLmNvbYINZXhhbXBsZTkxLmNvbYIN ZXhhbXBsZTkyLmNvbYINZXhhbXBsZTkzLmNvbYINZXhhbXBsZTk0LmNvbYINZXhh bXBsZTk1LmNvbYINZXhhbXBsZTk2LmNvbYINZXhhbXBsZTk3LmNvbYINZXhhbXBs ZTk4LmNvbYINZXhhbXBsZTk5LmNvbYIOZXhhbXBsZTEwMC5jb20wDQYJKoZIhvcN AQELBQADQQBEunJbKUXcyNKTSfA0pKRyWNiKmkoBqYgfZS6eHNrNH/hjFzHtzyDQ XYHHK6kgEWBvHfRXGmqhFvht+b1tQKkG -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/cert-idnsans.pem0000644000076500000240000000351200000000000017624 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIIFNjCCBOCgAwIBAgIJAP4rNqqOKifCMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV BAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMScwJQYDVQQLDB5FbGVjdHJv bmljIEZyb250aWVyIEZvdW5kYXRpb24xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4X DTE2MDEwNjIwMDg1OFoXDTE2MDEwNzIwMDg1OFowZDELMAkGA1UECAwCQ0ExFjAU BgNVBAcMDVNhbiBGcmFuY2lzY28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRp ZXIgRm91bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B AQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580 rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4IDczCCA28wCQYDVR0T BAIwADALBgNVHQ8EBAMCBeAwggNTBgNVHREEggNKMIIDRoJiz4PPhM+Fz4bPh8+I z4nPis+Lz4zPjc+Oz4/PkM+Rz5LPk8+Uz5XPls+Xz5jPmc+az5vPnM+dz57Pn8+g z6HPos+jz6TPpc+mz6fPqM+pz6rPq8+sz63Prs+vLmludmFsaWSCYs+wz7HPss+z z7TPtc+2z7fPuM+5z7rPu8+8z73Pvs+/2YHZgtmD2YTZhdmG2YfZiNmJ2YrZi9mM 2Y3ZjtmP2ZDZkdmS2ZPZlNmV2ZbZl9mY2ZnZmtmb2ZzZnS5pbnZhbGlkgmLZntmf 2aDZodmi2aPZpNml2abZp9mo2anZqtmr2azZrdmu2a/ZsNmx2bLZs9m02bXZttm3 2bjZudm62bvZvNm92b7Zv9qA2oHagtqD2oTahdqG2ofaiNqJ2oouaW52YWxpZIJi 2ovajNqN2o7aj9qQ2pHaktqT2pTaldqW2pfamNqZ2pram9qc2p3antqf2qDaodqi 2qPapNql2qbap9qo2qnaqtqr2qzardqu2q/asNqx2rLas9q02rXattq3LmludmFs aWSCYtq42rnautq72rzavdq+2r/bgNuB24Lbg9uE24XbhtuH24jbiduK24vbjNuN 247bj9uQ25HbktuT25TblduW25fbmNuZ25rbm9uc253bntuf26Dbodui26PbpC5p bnZhbGlkgnjbpdum26fbqNup26rbq9us263brtuv27Dbsduy27PbtNu127bbt9u4 27nbutu74aCg4aCh4aCi4aCj4aCk4aCl4aCm4aCn4aCo4aCp4aCq4aCr4aCs4aCt 4aCu4aCv4aCw4aCx4aCy4aCz4aC04aC1LmludmFsaWSCgY/hoLbhoLfhoLjhoLnh oLrhoLvhoLzhoL3hoL7hoL/hoYDhoYHhoYLhoYPhoYThoYXhoYbhoYfhoYjhoYnh oYrhoYvhoYzhoY3hoY7hoY/hoZDhoZHhoZLhoZPhoZThoZXhoZbhoZfhoZjhoZnh oZrhoZvhoZzhoZ3hoZ7hoZ/hoaDhoaHhoaIuaW52YWxpZIJE4aGj4aGk4aGl4aGm 4aGn4aGo4aGp4aGq4aGr4aGs4aGt4aGu4aGv4aGw4aGx4aGy4aGz4aG04aG14aG2 LmludmFsaWQwDQYJKoZIhvcNAQELBQADQQAzOQL/54yXxln87/YvEQbBm9ik9zoT TxEkvnZ4kmTRhDsUPtRjMXhY2FH7LOtXKnJQ7POUB7AsJ2Z6uq2w623G -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/cert-san.pem0000644000076500000240000000142200000000000016744 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIICFjCCAcCgAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR 7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c +pVE6K+EdE/twuUCAwEAAaM2MDQwCQYDVR0TBAIwADAnBgNVHREEIDAeggtleGFt cGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA0EASuvNKFTF nTJsvnSXn52f4BMZJJ2id/kW7+r+FJRm+L20gKQ1aqq8d3e/lzRUrv5SMf1TAOe7 RDjyGMKy5ZgM2w== -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/cert.der0000644000076500000240000000140300000000000016155 0ustar00bmwstaff00 2WZd0  *H  010U example.com0 151028072441Z 151127072441Z010U example.com0"0  *H 0 |0w3$Gav1.k>=ae\u[DnXߋ":ājP]teMp*8(if3-/e\vui DKlK˷ nal3g3$aLOGL) 3S񪓪̀,˞j s|H"0yc_M=.ۢh5JIh^+WɯFWs;?Gs9_̆eƞT4)kP0N0U6  )+eX70U#06  )+eX70 U00  *H  T2O]J7 ԣN^ x1[IK,=^2k뮫mHG[2-mlhfll@'DpH4C'{[C c60u;d FAzk&eP Ǫa3EuPjW3u ,S~C6Q8YOfs UpM!ȑ)+m,(hE {b KRVhŗ ;lv1././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/cert.pem0000644000076500000240000000130500000000000016165 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIIB3jCCAYigAwIBAgICBTkwDQYJKoZIhvcNAQELBQAwdzELMAkGA1UEBhMCVVMx ETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3IxKzApBgNVBAoM IlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDASBgNVBAMMC2V4 YW1wbGUuY29tMB4XDTE0MTIxMTIyMzQ0NVoXDTE0MTIxODIyMzQ0NVowdzELMAkG A1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIwEAYDVQQHDAlBbm4gQXJib3Ix KzApBgNVBAoMIlVuaXZlcnNpdHkgb2YgTWljaGlnYW4gYW5kIHRoZSBFRkYxFDAS BgNVBAMMC2V4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKx1c7RR 7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79vukFhN9HFoHZiUvOjm0c +pVE6K+EdE/twuUCAwEAATANBgkqhkiG9w0BAQsFAANBAC24z0IdwIVKSlntksll vr6zJepBH5fMndfk3XJp10jT6VE+14KNtjh02a56GoraAvJAT5/H67E8GvJ/ocNn B/o= -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/critical-san.pem0000644000076500000240000000322300000000000017602 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIIErTCCA5WgAwIBAgIKETb7VQAAAAAdGTANBgkqhkiG9w0BAQsFADCBkTELMAkG A1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5 MRUwEwYDVQQKEwxWZW5hZmksIEluYy4xHzAdBgNVBAsTFkRlbW9uc3RyYXRpb24g U2VydmljZXMxIjAgBgNVBAMTGVZlbmFmaSBFeGFtcGxlIElzc3VpbmcgQ0EwHhcN MTcwNzEwMjMxNjA1WhcNMTcwODA5MjMxNjA1WjAAMIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEA7CU5qRIzCs9hCRiSUvLZ8r81l4zIYbx1V1vZz6x1cS4M 0keNfFJ1wB+zuvx80KaMYkWPYlg4Rsm9Ok3ZapakXDlaWtrfg78lxtHuPw1o7AYV EXDwwPkNugLMJfYw5hWYSr8PCLcOJoY00YQ0fJ44L+kVsUyGjN4UTRRZmOh/yNVU 0W12dTCz4X7BAW01OuY6SxxwewnW3sBEep+APfr2jd/oIx7fgZmVB8aRCDPj4AFl XINWIwxmptOwnKPbwLN/vhCvJRUkO6rA8lpYwQkedFf6fHhqi2Sq/NCEOg4RvMCF fKbMpncOXxz+f4/i43SVLrPz/UyhjNbKGJZ+zFrQowIDAQABo4IBlTCCAZEwPgYD VR0RAQH/BDQwMoIbY2hpY2Fnby1jdWJzLnZlbmFmaS5leGFtcGxlghNjdWJzLnZl bmFmaS5leGFtcGxlMB0GA1UdDgQWBBTgKZXVSFNyPHHtO/phtIALPcCF5DAfBgNV HSMEGDAWgBT/JJ6Wei/pzf+9DRHuv6Wgdk2HsjBSBgNVHR8ESzBJMEegRaBDhkFo dHRwOi8vcGtpLnZlbmFmaS5leGFtcGxlL2NybC9WZW5hZmklMjBFeGFtcGxlJTIw SXNzdWluZyUyMENBLmNybDA6BggrBgEFBQcBAQQuMCwwKgYIKwYBBQUHMAGGHmh0 dHA6Ly9wa2kudmVuYWZpLmV4YW1wbGUvb2NzcDAOBgNVHQ8BAf8EBAMCBaAwPQYJ KwYBBAGCNxUHBDAwLgYmKwYBBAGCNxUIhIDLGYTvsSSEnZ8ehvD5UofP4hMEgobv DIGy4mcCAWQCAQIwEwYDVR0lBAwwCgYIKwYBBQUHAwEwGwYJKwYBBAGCNxUKBA4w DDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEA3YW4t1AzxEn384OqdU6L ny8XkMhWpRM0W0Z9ZC3gRZKbVUu49nG/KB5hbVn/de33zdX9HOZJKc0vXzkGZQUs OUCCsKX4VKzV5naGXOuGRbvV4CJh5P0kPlDzyb5t312S49nJdcdBf0Y/uL5Qzhst bXy8qNfFNG3SIKKRAUpqE9OVIl+F+JBwexa+v/4dFtUOqMipfXxB3TaxnDqvU1dS yO34ZTvIMGXJIZ5nn/d/LNc3N3vBg2SHkMpladqw0Hr7mL0bFOe0b+lJgkDP06Be n08fikhz1j2AW4/ZHa9w4DUz7J21+RtHMhh+Vd1On0EAeZ563svDe7Z+yrg6zOVv KA== -----END CERTIFICATE-----././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr-100sans.pem0000644000076500000240000000502100000000000017201 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIHNTCCBt8CAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 lUTor4R0T+3C5QIDAQABoIIGFDCCBhAGCSqGSIb3DQEJDjGCBgEwggX9MAkGA1Ud EwQCMAAwCwYDVR0PBAQDAgXgMIIF4QYDVR0RBIIF2DCCBdSCDGV4YW1wbGUxLmNv bYIMZXhhbXBsZTIuY29tggxleGFtcGxlMy5jb22CDGV4YW1wbGU0LmNvbYIMZXhh bXBsZTUuY29tggxleGFtcGxlNi5jb22CDGV4YW1wbGU3LmNvbYIMZXhhbXBsZTgu Y29tggxleGFtcGxlOS5jb22CDWV4YW1wbGUxMC5jb22CDWV4YW1wbGUxMS5jb22C DWV4YW1wbGUxMi5jb22CDWV4YW1wbGUxMy5jb22CDWV4YW1wbGUxNC5jb22CDWV4 YW1wbGUxNS5jb22CDWV4YW1wbGUxNi5jb22CDWV4YW1wbGUxNy5jb22CDWV4YW1w bGUxOC5jb22CDWV4YW1wbGUxOS5jb22CDWV4YW1wbGUyMC5jb22CDWV4YW1wbGUy MS5jb22CDWV4YW1wbGUyMi5jb22CDWV4YW1wbGUyMy5jb22CDWV4YW1wbGUyNC5j b22CDWV4YW1wbGUyNS5jb22CDWV4YW1wbGUyNi5jb22CDWV4YW1wbGUyNy5jb22C DWV4YW1wbGUyOC5jb22CDWV4YW1wbGUyOS5jb22CDWV4YW1wbGUzMC5jb22CDWV4 YW1wbGUzMS5jb22CDWV4YW1wbGUzMi5jb22CDWV4YW1wbGUzMy5jb22CDWV4YW1w bGUzNC5jb22CDWV4YW1wbGUzNS5jb22CDWV4YW1wbGUzNi5jb22CDWV4YW1wbGUz Ny5jb22CDWV4YW1wbGUzOC5jb22CDWV4YW1wbGUzOS5jb22CDWV4YW1wbGU0MC5j b22CDWV4YW1wbGU0MS5jb22CDWV4YW1wbGU0Mi5jb22CDWV4YW1wbGU0My5jb22C DWV4YW1wbGU0NC5jb22CDWV4YW1wbGU0NS5jb22CDWV4YW1wbGU0Ni5jb22CDWV4 YW1wbGU0Ny5jb22CDWV4YW1wbGU0OC5jb22CDWV4YW1wbGU0OS5jb22CDWV4YW1w bGU1MC5jb22CDWV4YW1wbGU1MS5jb22CDWV4YW1wbGU1Mi5jb22CDWV4YW1wbGU1 My5jb22CDWV4YW1wbGU1NC5jb22CDWV4YW1wbGU1NS5jb22CDWV4YW1wbGU1Ni5j b22CDWV4YW1wbGU1Ny5jb22CDWV4YW1wbGU1OC5jb22CDWV4YW1wbGU1OS5jb22C DWV4YW1wbGU2MC5jb22CDWV4YW1wbGU2MS5jb22CDWV4YW1wbGU2Mi5jb22CDWV4 YW1wbGU2My5jb22CDWV4YW1wbGU2NC5jb22CDWV4YW1wbGU2NS5jb22CDWV4YW1w bGU2Ni5jb22CDWV4YW1wbGU2Ny5jb22CDWV4YW1wbGU2OC5jb22CDWV4YW1wbGU2 OS5jb22CDWV4YW1wbGU3MC5jb22CDWV4YW1wbGU3MS5jb22CDWV4YW1wbGU3Mi5j b22CDWV4YW1wbGU3My5jb22CDWV4YW1wbGU3NC5jb22CDWV4YW1wbGU3NS5jb22C DWV4YW1wbGU3Ni5jb22CDWV4YW1wbGU3Ny5jb22CDWV4YW1wbGU3OC5jb22CDWV4 YW1wbGU3OS5jb22CDWV4YW1wbGU4MC5jb22CDWV4YW1wbGU4MS5jb22CDWV4YW1w bGU4Mi5jb22CDWV4YW1wbGU4My5jb22CDWV4YW1wbGU4NC5jb22CDWV4YW1wbGU4 NS5jb22CDWV4YW1wbGU4Ni5jb22CDWV4YW1wbGU4Ny5jb22CDWV4YW1wbGU4OC5j b22CDWV4YW1wbGU4OS5jb22CDWV4YW1wbGU5MC5jb22CDWV4YW1wbGU5MS5jb22C DWV4YW1wbGU5Mi5jb22CDWV4YW1wbGU5My5jb22CDWV4YW1wbGU5NC5jb22CDWV4 YW1wbGU5NS5jb22CDWV4YW1wbGU5Ni5jb22CDWV4YW1wbGU5Ny5jb22CDWV4YW1w bGU5OC5jb22CDWV4YW1wbGU5OS5jb22CDmV4YW1wbGUxMDAuY29tMA0GCSqGSIb3 DQEBCwUAA0EAW05UMFavHn2rkzMyUfzsOvWzVNlm43eO2yHu5h5TzDb23gkDnNEo duUAbQ+CLJHYd+MvRCmPQ+3ZnaPy7l/0Hg== -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr-6sans.pem0000644000076500000240000000124400000000000017051 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIBuzCCAWUCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE1pY2hpZ2FuMRIw EAYDVQQHEwlBbm4gQXJib3IxDDAKBgNVBAoTA0VGRjEfMB0GA1UECxMWVW5pdmVy c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wXDANBgkqhkiG 9w0BAQEFAANLADBIAkEA9LYRcVE3Nr+qleecEcX8JwVDnjeG1X7ucsCasuuZM0e0 9cmYuUzxIkMjO/9x4AVcvXXRXPEV+LzWWkfkTlzRMwIDAQABoIGGMIGDBgkqhkiG 9w0BCQ4xdjB0MHIGA1UdEQRrMGmCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZ4IL ZXhhbXBsZS5uZXSCDGV4YW1wbGUuaW5mb4IVc3ViZG9tYWluLmV4YW1wbGUuY29t ghtvdGhlci5zdWJkb21haW4uZXhhbXBsZS5jb20wDQYJKoZIhvcNAQELBQADQQBd k4BE5qvEvkYoZM/2++Xd9RrQ6wsdj0QiJQCozfsI4lQx6ZJnbtNc7HpDrX4W6XIv IvzVBz/nD11drfz/RNuX -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr-idnsans.pem0000644000076500000240000000323300000000000017456 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIEpzCCBFECAQAwZDELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lz Y28xJzAlBgNVBAsMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91bmRhdGlvbjEUMBIG A1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEArHVztFHt H92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE30cWgdmJS86ObRz6 lUTor4R0T+3C5QIDAQABoIIDhjCCA4IGCSqGSIb3DQEJDjGCA3MwggNvMAkGA1Ud EwQCMAAwCwYDVR0PBAQDAgXgMIIDUwYDVR0RBIIDSjCCA0aCYs+Dz4TPhc+Gz4fP iM+Jz4rPi8+Mz43Pjs+Pz5DPkc+Sz5PPlM+Vz5bPl8+Yz5nPms+bz5zPnc+ez5/P oM+hz6LPo8+kz6XPps+nz6jPqc+qz6vPrM+tz67Pry5pbnZhbGlkgmLPsM+xz7LP s8+0z7XPts+3z7jPuc+6z7vPvM+9z77Pv9mB2YLZg9mE2YXZhtmH2YjZidmK2YvZ jNmN2Y7Zj9mQ2ZHZktmT2ZTZldmW2ZfZmNmZ2ZrZm9mc2Z0uaW52YWxpZIJi2Z7Z n9mg2aHZotmj2aTZpdmm2afZqNmp2arZq9ms2a3Zrtmv2bDZsdmy2bPZtNm12bbZ t9m42bnZutm72bzZvdm+2b/agNqB2oLag9qE2oXahtqH2ojaidqKLmludmFsaWSC YtqL2ozajdqO2o/akNqR2pLak9qU2pXaltqX2pjamdqa2pvanNqd2p7an9qg2qHa otqj2qTapdqm2qfaqNqp2qraq9qs2q3artqv2rDasdqy2rPatNq12rbaty5pbnZh bGlkgmLauNq52rrau9q82r3avtq/24DbgduC24PbhNuF24bbh9uI24nbituL24zb jduO24/bkNuR25Lbk9uU25XbltuX25jbmdua25vbnNud257bn9ug26Hbotuj26Qu aW52YWxpZIJ426Xbptun26jbqduq26vbrNut267br9uw27Hbstuz27Tbtdu227fb uNu527rbu+GgoOGgoeGgouGgo+GgpOGgpeGgpuGgp+GgqOGgqeGgquGgq+GgrOGg reGgruGgr+GgsOGgseGgsuGgs+GgtOGgtS5pbnZhbGlkgoGP4aC24aC34aC44aC5 4aC64aC74aC84aC94aC+4aC/4aGA4aGB4aGC4aGD4aGE4aGF4aGG4aGH4aGI4aGJ 4aGK4aGL4aGM4aGN4aGO4aGP4aGQ4aGR4aGS4aGT4aGU4aGV4aGW4aGX4aGY4aGZ 4aGa4aGb4aGc4aGd4aGe4aGf4aGg4aGh4aGiLmludmFsaWSCROGho+GhpOGhpeGh puGhp+GhqOGhqeGhquGhq+GhrOGhreGhruGhr+GhsOGhseGhsuGhs+GhtOGhteGh ti5pbnZhbGlkMA0GCSqGSIb3DQEBCwUAA0EAeNkY0M0+kMnjRo6dEUoGE4dX9fEr dfGrpPUBcwG0P5QBdZJWvZxTfRl14yuPYHbGHULXeGqRdkU6HK5pOlzpng== -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr-nosans.pem0000644000076500000240000000070400000000000017320 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIBFTCBwAIBADBbMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRQwEgYDVQQDDAtleGFt cGxlLm9yZzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD0thFxUTc2v6qV55wRxfwn BUOeN4bVfu5ywJqy65kzR7T1yZi5TPEiQyM7/3HgBVy9ddFc8RX4vNZaR+ROXNEz AgMBAAGgADANBgkqhkiG9w0BAQsFAANBAMikGL8Ch7hQCStXH7chhDp6+pt2+VSo wgsrPQ2Bw4veDMlSemUrH+4e0TwbbntHfvXTDHWs9P3BiIDJLxFrjuA= -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr-san.pem0000644000076500000240000000107600000000000016603 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIBbjCCARgCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG 9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoDowOAYJKoZIhvcN AQkOMSswKTAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29t MA0GCSqGSIb3DQEBCwUAA0EAZGBM8J1rRs7onFgtc76mOeoT1c3v0ZsEmxQfb2Wy tmReY6X1N4cs38D9VSow+VMRu2LWkKvzS7RUFSaTaeQz1A== -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr.der0000644000076500000240000000113700000000000016013 0ustar00bmwstaff0[0C010U example.com0"0  *H 0 |0w3$Gav1.k>=ae\u[DnXߋ":ājP]teMp*8(if3-/e\vui DKlK˷ nal3g3$aLOGL) 3S񪓪̀,˞j s|H"0yc_M=.ۢh5JIh^+WɯFWs;?Gs9_̆eƞT4)k0  *H  r0J0*;s k[-I%I!6gu%i8O}D6>:}_QMKYB4a861p @TEXUBï Pr>h6(.Μhw ٖqaQ_PkN=EКS?E %OdѻK=.gh첶Be+_)uUusZQÍV8s0Qr;`z=FI././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/csr.pem0000644000076500000240000000104600000000000016021 0ustar00bmwstaff-----BEGIN CERTIFICATE REQUEST----- MIIBXTCCAQcCAQAweTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCE1pY2hpZ2FuMRIw EAYDVQQHDAlBbm4gQXJib3IxDDAKBgNVBAoMA0VGRjEfMB0GA1UECwwWVW5pdmVy c2l0eSBvZiBNaWNoaWdhbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG 9w0BAQEFAANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3f p580rv2+6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoCkwJwYJKoZIhvcN AQkOMRowGDAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAANB AHJH/O6BtC9aGzEVCMGOZ7z9iIRHWSzr9x/bOzn7hLwsbXPAgO1QxEwL+X+4g20G n9XBE1N9W6HCIEut2d8wACg= -----END CERTIFICATE REQUEST----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/dsa512_key.pem0000644000076500000240000000125400000000000017102 0ustar00bmwstaff-----BEGIN DSA PARAMETERS----- MIGdAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqfn6GC OixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSPAkEA qfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xmrfvl 41pgNJpgu99YOYqPpS0g7A== -----END DSA PARAMETERS----- -----BEGIN DSA PRIVATE KEY----- MIH5AgEAAkEAwebEoGBfokKQeALHHnAZMQwYU35ILEBdV8oUmzv7qpSVUoHihyqf n6GCOixAKSP8EJYcTilIqPbFbfFyOPlbLwIVANoFHEDiQgknAvKrG78pHzAJdQSP AkEAqfka5Bnl+CeEMpzVZGrOVqZE/LFdZK9eT6YtWjzqtIkf3hwXUVxJsTnBG4xm rfvl41pgNJpgu99YOYqPpS0g7AJATQ2LUzjGQSM6UljcPY5I2OD9THkUR9kH2tth zZd70UoI9btrVaTizgqYShuok94glSQNK0H92JgUk3scJPaAkAIVAMDn61h6vrCE mNv063So6E+eYaIN -----END DSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/ec_p256_key.pem0000644000076500000240000000045500000000000017250 0ustar00bmwstaff-----BEGIN EC PARAMETERS----- BggqhkjOPQMBBw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MHcCAQEEIMUXjUASqqk7YRvKE5gXYaeBEGCJkitXVan2tssjJ9q3oAoGCCqGSM49 AwEHoUQDQgAEjjQtV+fA7J/tK8dPzYq7jRPNjF8r5p6LW2R25S2Gw5UQ8DDz/zPs 9gqwcfqGUZKWxbEWgWXv7S8zRBEZuae8Jw== -----END EC PRIVATE KEY-----././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/ec_p384_key.pem0000644000076500000240000000054600000000000017253 0ustar00bmwstaff-----BEGIN EC PARAMETERS----- BgUrgQQAIg== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MIGkAgEBBDBHGWgnk8VgEwuXadVIOaCXw+MJ7qVrSHSiAOZri9LExJGSioGpGtZa fzqLGitkNAygBwYFK4EEACKhZANiAAS0iGk20ReRp1RutjzitgaACkUzVf/eXcRZ BG44/Uha3GVN+2tlDCd4lkXSTR5GfTQpbYan5NOENeZn70wk+cPyG/5fXfRJW99l GXVrTgc9XAhu8t7zua8D+K45r6bJTK8= -----END EC PRIVATE KEY-----././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/ec_p521_key.pem0000644000076500000240000000066300000000000017244 0ustar00bmwstaff-----BEGIN EC PARAMETERS----- BgUrgQQAIw== -----END EC PARAMETERS----- -----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIB9I82YbB0mhnHdWOnwP4Ag0Gl2x+tKYnfTWNEaqW0+cztSrI0OD/W WtjJo+8fC+2QWGxV4l1sHpXebBnTG8NWS2OgBwYFK4EEACOhgYkDgYYABABZHZen CswZj9fFPH336T+CGTdnhA+OYQ3MET5eWXgpcB3C7ZzATLlmBRTvzc5ugy9jIaTd MyNm3DCyXfeWmNrT7AGL2at8Aco7e50HfJ2DKoRTQi94//lOhj1mXrdYWAWP12R6 wuMiFdEeCZ7LEQGPXqnNRBJaQnOIc4orNY4ldYkeFg== -----END EC PRIVATE KEY-----././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/rsa1024_key.pem0000644000076500000240000000156700000000000017206 0ustar00bmwstaff-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCaifO0fGlcAcjjcYEAPYcIL0Hf0KiNa9VCJ14RBdlZxLWRrVFi 4tdNCKSKqzKuKrrA8DWd4PHFD7UpLyRrPPXY6GozAyCT+5UFBClGJ2KyNKu+eU6/ w4C1kpO4lpeXs8ptFc1lA9P8V1M/MkWzTE402nPNK0uUmZNo2tsFpGJUSQIDAQAB AoGAFjLWxQhSAhtnhfRZ+XTdHrnbFpFchOQGgDgzdPKIJDLzefeRh0jacIBbUmgB Ia+Vn/1hVkpnsEzvUvkonBbnoYWlYVQdpNTmrrew7SOztf8/1fYCsSkyDAvqGTXc TmHM0PaLS+junoWcKOvQRVb0N3k+43OnBkr2b393Sx30qGECQQDNO2IBWOsYs8cB CZQAZs8zBlbwBFZibqovqpLwXt9adBIsT9XzgagGbJMpzSuoHTUn3QqqJd9uHD8X UTmmoh4NAkEAwMRauo+PlNj8W1lusflko52KL17+E5cmeOERM2xvhZNpO7d3/1ak Co9dxVMicrYSh7jXbcXFNt3xNDTv6Dg8LQJAPuJwMDt/pc0IMCAwMkNOP7M0lkyt 73E7QmnAplhblcq0+tDnnLpgsr84BHnyY4u3iuRm7SW3pXSQPGPOB2nrTQJANBXa HgakWSe4KEal7ljgpITwzZPxOwHgV1EZALgP+hu2l3gfaFLUyDWstKCd8jjYEOwU 6YhCnWyiu+SB3lEzkQJBAJapJpfypFyY8kQNYlYILLBcPu5fmy3QUZKHJ4L3rIVJ c2UTLMeBBgGFHT04CtWntmjwzSv+V6lwiCxKXsIUySc= -----END RSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/rsa2048_cert.pem0000644000076500000240000000241600000000000017354 0ustar00bmwstaff-----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIJALVG/VbBb5U7MA0GCSqGSIb3DQEBCwUAMFsxCzAJBgNV BAYTAkFVMQswCQYDVQQIDAJXQTEeMBwGA1UEBwwVVGhlIG1pZGRsZSBvZiBub3do ZXJlMR8wHQYDVQQKDBZDZXJ0Ym90IFRlc3QgQ2VydHMgSW5jMCAXDTE2MTEyODIx MzUzN1oYDzIyOTAwOTEzMjEzNTM3WjBbMQswCQYDVQQGEwJBVTELMAkGA1UECAwC V0ExHjAcBgNVBAcMFVRoZSBtaWRkbGUgb2Ygbm93aGVyZTEfMB0GA1UECgwWQ2Vy dGJvdCBUZXN0IENlcnRzIEluYzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC ggEBANoVT1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svN PSa+oPTK7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm 7Gj6m2EzpSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFn xvvOjBYop7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTD g7P4UAuFkejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1 RAeEeRTkh0WjUfltoem/5f8bIdsCAwEAAaNTMFEwHQYDVR0OBBYEFHy+bEYqwvFU uQLTkIfQ36AM2DQiMB8GA1UdIwQYMBaAFHy+bEYqwvFUuQLTkIfQ36AM2DQiMA8G A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAH3ANVzB59FcunZV/F8T RiCD6/gV7Jc3CswU8N8tVjzMCg2jOdTFF9iYZzNNKQvG13o/n5LkQr/lkKRQkWTx nkE5WZbR7vNqlzXgPa9NBiK5rPjgSt8azPW+Skct3Bj4B3PhTMSpoQ7PsUJ8UeV8 kTNR5xrRLt6/mLfRJTXWXBM43GEZi8lL5q0nqz0tPGISADshHMo6ZlUu5Hvfp5v+ aonpO4sVS9hGOVxjGNMXYApEUy4jid9jjAfEk6jeELJMbXGLy/botFgIJK/QPe6P AfbdFgtg/qzG7Uy0A1iXXfWdgwmVrhCoGYYWCn4yWCAm894QKtdim87CHSDP0WUf Esg= -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/rsa2048_key.pem0000644000076500000240000000325000000000000017204 0ustar00bmwstaff-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDaFU9aXb0VFATq r5uzNnmyxB1e4YoFB+6gBlGRJHz+luGK4mFWPiB6x9bLzT0mvqD0yu8HgzZk9dXo VpxshAiDPc9q9h4vclVdMnMYeIVkODbfAQCmcAsGl6clpuxo+pthM6UjeQ3eu4OW AEAa1FIdfkUWxYvXNC7P+mThMTL34PJKreHNrFwoB9SBZ8b7zowWKKe8YdMdy9QJ MmsdLcPKoEKf8+ZpLqZlQzSWDxZm8xTzDcg0etXnekFEw4Oz+FALhZHoyy8xb8+W Gju7wvobmk7j4SJwIac6qa+MlPeyiI6LNv/NyLbvwr/FNUQHhHkU5IdFo1H5baHp v+X/GyHbAgMBAAECggEAURFe4C68XRuGAF+rN2Fmt+djK6QXlGswb1gp9hRkSpd3 3BLvMAoENOAYnsX6l26Bkr3lQRurmrgv/iBEIaqrJ25QrmgzLFwKE4zvcAdNPsYO z7MltLktwBOb1MlKVHPkUqvKFXeoikWWUqphKhgHNmN7900UALmrNTDVU0jgs3fB o35o8d5SjoC52K4wCTjhPyjt4cdbfbziRs2qFhfGdawidRO1xLlDM4tTTW+5yWGK lt0SwyvDVC6XWeNoT3nXyKjXWP7hcYqm0iS7ffL9YzEC2RXNGQUqeR50i9Y0rDdH Vqcr+Rqio2ww68zbDWBpC/jU133BSoHuSE1wstxIkQKBgQDxlEr42WJfgdajbZ1a hUIeLEgvhezLmD1hcYwZuQCLgizmY2ovvmeAH74koCDEsUUQunPYHsRla7wT3q1/ IkR1KgJPwESpkQaKuAqxeEAkv7Gn8Lzcn22jCoRCfGA68wKJz2ECFZDc0RDvRrT/ 9GhiiGUoO47jv9ezrSDO1eu5/QKBgQDnGfYVMNLiA0fy4AxSyY2vdo7vruOFGpRP n94gwxZ+0dQDWHzn3J4rHivxtcyd/MOZv4I8PtYK7tmmjYv1ngQ6sGl4p8bpUtwj 9++/B1CyB1W5/VPqMkd+Sj0dbejycME55+F6/r4basPXxBFFCfknjAlVvyvbBhUy ftNpHxZGtwKBgChJM4t2LPqCW3nbgL8ks9b2SX9rVQbKt4m1dsifWmDpb3VoJMAb f4UVRg8ziONkMIFOppzm3JeRNMcXflVSMJpdTA9in9CrN60QbfAUfpXiRc0cz1H3 YEAtM8smlKGf/s9efu3rDMJWNv3AC9UXPAUae8wOypBeYKk8+NilQe89AoGAXEA3 xFO+CqyGnwQixzVf0qf//NuSRQLMK1DEyc02gJ9gA4niKmgd11Zu8kjBClvo9MnG wifPJ4Qa6+pa8UwHoinjoF9Q/rit2cnSMS5JXxegd+MRCU7SzS3zYXkLYSPzbhsL Hh7sYmNnFA1XW3jUtZ2n6EusxPyTn5mS6MaZDNcCgYBelFKFjNIQ50NbOnm8DewK jUd5OFKowKodlQVcHiF9CVbjvpN8ZPRcBSmqDU4kpT/rmcybVoL6Zfa/zWkw8+Oh QxKb3BYf5vRUMd/RA+/t5KG4ZOIIYB3qoltAYlhVaINukL6cGVG1qvV/ntcsfsn6 kmf1UgGFcKrJuXgwEtTVxw== -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/rsa256_key.pem0000644000076500000240000000045200000000000017124 0ustar00bmwstaff-----BEGIN RSA PRIVATE KEY----- MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3 rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt -----END RSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/testdata/rsa512_key.pem0000644000076500000240000000075500000000000017125 0ustar00bmwstaff-----BEGIN RSA PRIVATE KEY----- MIIBOgIBAAJBAKx1c7RR7R/drnBSQ/zfx1vQLHUbFLh1AQQQ5R8DZUXd36efNK79 vukFhN9HFoHZiUvOjm0c+pVE6K+EdE/twuUCAwEAAQJAMbrEnJCrQe8YqAbw1/Bn elAzIamndfE3U8bTavf9sgFpS4HL83rhd6PDbvx81ucaJAT/5x048fM/nFl4fzAc mQIhAOF/a9o3EIsDKEmUl+Z1OaOiUxDF3kqWSmALEsmvDhwXAiEAw8ljV5RO/rUp Zu2YMDFq3MKpyyMgBIJ8CxmGRc6gCmMCIGRQzkcmhfqBrhOFwkmozrqIBRIKJIjj 8TRm2LXWZZ2DAiAqVO7PztdNpynugUy4jtbGKKjBrTSNBRGA7OHlUgm0dQIhALQq 6oGU29Vxlvt3k0vmiRKU4AVfLyNXIGtcWcNG46h/ -----END RSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1632778600.0 josepy-1.10.0/tests/util_test.py0000644000076500000240000002022100000000000015300 0ustar00bmwstaff"""Tests for josepy.util.""" import functools import unittest import test_util class ComparableX509Test(unittest.TestCase): """Tests for josepy.util.ComparableX509.""" def setUp(self): # test_util.load_comparable_{csr,cert} return ComparableX509 self.req1 = test_util.load_comparable_csr('csr.pem') self.req2 = test_util.load_comparable_csr('csr.pem') self.req_other = test_util.load_comparable_csr('csr-san.pem') self.cert1 = test_util.load_comparable_cert('cert.pem') self.cert2 = test_util.load_comparable_cert('cert.pem') self.cert_other = test_util.load_comparable_cert('cert-san.pem') def test_getattr_proxy(self): self.assertIs(self.cert1.has_expired(), True) def test_eq(self): self.assertEqual(self.req1, self.req2) self.assertEqual(self.cert1, self.cert2) def test_ne(self): self.assertNotEqual(self.req1, self.req_other) self.assertNotEqual(self.cert1, self.cert_other) def test_ne_wrong_types(self): self.assertNotEqual(self.req1, 5) self.assertNotEqual(self.cert1, 5) def test_hash(self): self.assertEqual(hash(self.req1), hash(self.req2)) self.assertNotEqual(hash(self.req1), hash(self.req_other)) self.assertEqual(hash(self.cert1), hash(self.cert2)) self.assertNotEqual(hash(self.cert1), hash(self.cert_other)) def test_repr(self): for x509 in self.req1, self.cert1: self.assertEqual(repr(x509), ''.format(x509.wrapped)) class ComparableRSAKeyTest(unittest.TestCase): """Tests for josepy.util.ComparableRSAKey.""" def setUp(self): # test_utl.load_rsa_private_key return ComparableRSAKey self.key = test_util.load_rsa_private_key('rsa256_key.pem') self.key_same = test_util.load_rsa_private_key('rsa256_key.pem') self.key2 = test_util.load_rsa_private_key('rsa512_key.pem') def test_getattr_proxy(self): self.assertEqual(256, self.key.key_size) def test_eq(self): self.assertEqual(self.key, self.key_same) def test_ne(self): self.assertNotEqual(self.key, self.key2) def test_ne_different_types(self): self.assertNotEqual(self.key, 5) def test_ne_not_wrapped(self): # pylint: disable=protected-access self.assertNotEqual(self.key, self.key_same._wrapped) def test_ne_no_serialization(self): from josepy.util import ComparableRSAKey self.assertNotEqual(ComparableRSAKey(5), ComparableRSAKey(5)) def test_hash(self): self.assertIsInstance(hash(self.key), int) self.assertEqual(hash(self.key), hash(self.key_same)) self.assertNotEqual(hash(self.key), hash(self.key2)) def test_repr(self): self.assertIs(repr(self.key).startswith( '