diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..429d332 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,9 @@ +# codecov.yml file, spec is visible: +# https://github.com/codecov/support/wiki/Codecov-Yaml +coverage: + status: + # pull-requests only + patch: + default: + # coverage may fall by <1% and still be considered "passing" + threshold: 1% diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fab0888 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,78 @@ +name: CI +on: + push: + branches: + - "*" + pull_request: + branches: + - master +jobs: + test: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + python-version: ["3.6", "3.7", "3.8", "3.9", "pypy3"] + os: [ubuntu-latest, macos-latest, windows-latest] + exclude: + - os: macos-latest + python-version: "pypy3" + - os: windows-latest + python-version: "pypy3" + runs-on: ${{ matrix.os }} + name: "${{ matrix.os }} Python: ${{ matrix.python-version }}" + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + pip install -U "pip>=21.1" + pip install -U setuptools + pip install -U "tox>=3.23.0,<4" codecov tox-gh-actions coverage + - name: Log python & pip versions + run: | + python --version + pip --version + - name: Run unit tests + run: tox + - name: "Coverage report" + run: coverage xml + - name: "Upload coverage to Codecov" + uses: "codecov/codecov-action@v1" + with: + fail_ci_if_error: true + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: | + pip install -U setuptools + pip install -U "tox>=3.23.0,<4" + - run: tox -e lint + package: + name: "Build & verify package" + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v2" + with: + python-version: "3.8" + - name: "Install build, check-wheel-content, and twine" + run: "python -m pip install build twine check-wheel-contents" + - name: "Build package" + run: "python -m build --sdist --wheel ." + - name: "List result" + run: "ls -l dist" + - name: "Check wheel contents" + run: "check-wheel-contents dist/*.whl" + - name: "Check long_description" + run: "python -m twine check dist/*" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04d0e35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# PyCharm +.idea/ + +# PyEnv +.python-version + +# PyTest +.pytest_cache/ + +# VSCode +.vscode/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d5a360a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,86 @@ +# Changelog # + +## 3.3.0 -- 2020-06-04 ## + +### News ### + +* Remove support for python 2.7 & 3.5 +* Add support for Python 3.9 +* Remove PyCrypto backend +* Fix deprecation warning from cryptography backend + +### Housekeeping ### + +* Switched from Travis CI to Github Actions +* Added iSort & Black +* Run CI Tests under Mac OS & Windows. +* Updated Syntax to use Python 3.6+ +* Upgrade to latest pytest, remove used dev requirements. + + +## 3.2.0 -- 2020-07-29 ## + +### News ### + +* This will be the last release supporting Python 2.7, 3.5, and the PyCrypto + backend. + +### Bug fixes and Improvements ### + +* Use hmac.compare_digest instead of our own constant_time_string_compare #163 +* Fix `to_dict` output, which should always be JSON encodeable. #139 and #165 + (fixes #127 and #137) +* Require setuptools >= 39.2.0 #167 (fixes #161) +* Emit a warning when verifying with a private key #168 (fixes #53 and #142) +* Avoid loading python-ecdsa when using the cryptography backend, and pinned + python-ecdsa dependency to <0.15 #178 + +### Housekeeping ### + +* Fixed some typos #160, #162, and #164 + + + +## 3.1.0 -- 2019-12-10 ## + +This is a greatly overdue release. + +### Features ### + +* Improve `JWT.decode()` #76 (fixes #75) +* Sort headers when serializing to allow for headless JWT #136 (fixes #80) +* Adjust dependency handling + - Use PyCryptodome instead of PyCrypto #83 + - Update package dependencies #124 (fixes #158) +* Avoid using deprecated methods #85 +* Support X509 certificates #107 +* Isolate and flesh out cryptographic backends to enable independent operation #129 (fixes #114) + - Remove pyca/cryptography backend's dependency on python-ecdsa #117 + - Remove pycrypto/dome backends' dependency on python-rsa #121 + - Make pyca/cryptography backend the preferred backend if multiple backends are present #122 + +### Bugfixes/Improvements ### + +* Enable flake8 check in tox/TravisCI #77 +* Fix `crytography` dependency typo #94 +* Trigger tests using `python setup.py test` #97 +* Properly raise an error if a claim is expected and not given #98 +* Typo fixes #110 +* Fix invalid RSA private key PKCS8 encoding by python-rsa backend #120 (fixes #119) +* Remove `future` dependency #134 (fixes #112) +* Fix incorrect use of `pytest.raises(message=...)` #141 +* Typo fix #143 +* Clarify sign docstring to allow for `dict` payload #150 + +### Housekeeping ### + +* Streamline the code a bit and update classifiers #87 +* Fix typo and rephrase `access_token` documentation #89 +* Code linting now mostly honors flake8 #101 +* Document using a `dict` for `jwt.encode` and `jwt.decode` #103 +* Include docs and tests in source distributions #111 +* Updating README descriptions of crypto backends #130 +* Document versioning policy #131 +* Add `CHANGELOG.rst` #132 (fixes #99) +* Simplify and extend `.travis.yml` #135 +* Move `CHANGELOG.rst` to `CHANGELOG.md` and update it #159 diff --git a/MANIFEST.in b/MANIFEST.in index 8fb0e6e..eaadccc 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,10 @@ include README.rst include LICENSE include requirements.txt include requirements-*.txt include tox.ini +include pytest.ini +include CHANGELOG.md +include VERSIONING.md graft docs graft tests diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 0f4788b..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,136 +0,0 @@ -Metadata-Version: 1.1 -Name: python-jose -Version: 3.1.0 -Summary: JOSE implementation in Python -Home-page: http://github.com/mpdavis/python-jose -Author: Michael Davis -Author-email: mike.philip.davis@gmail.com -License: MIT -Description: python-jose - =========== - - A JOSE implementation in Python - - |Build Status| |Coverage Status| |Docs| - - Docs are available on ReadTheDocs_. - - The JavaScript Object Signing and Encryption (JOSE) technologies - JSON - Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and - JSON Web Algorithms (JWA) - collectively can be used to encrypt and/or - sign content using a variety of algorithms. While the full set of - permutations is extremely large, and might be daunting to some, it is - expected that most applications will only use a small set of algorithms - to meet their needs. - - - Installation - ------------ - - :: - - $ pip install python-jose[cryptography] - - - Cryptographic Backends - ---------------------- - - As of 3.1.0, python-jose implements four different cryptographic backends. - The backend must be selected as an extra when installing python-jose. - If you do not select a backend, the native-python backend will be installed. - - Unless otherwise noted, all backends support all operations. - - Due to complexities with setuptools, the native-python backend is always installed, - even if you select a different backend on install. - We recommend that you remove unnecessary dependencies in production. - - #. cryptography - - * This backend uses `pyca/cryptography`_ for all cryptographic operations. - This is the recommended backend and is selected over all other backends if any others are present. - * Installation: ``pip install python-jose[cryptography]`` - * Unused dependencies: - - * ``rsa`` - * ``ecdsa`` - * ``pyasn1`` - - #. pycryptodome - - * This backend uses `pycryptodome`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycryptodome]`` - * Unused dependencies: - - * ``rsa`` - - #. native-python - - * This backend uses `python-rsa`_ and `python-ecdsa`_ for all cryptographic operations. - This backend is always installed but any other backend will take precedence if one is installed. - * Installation: ``pip install python-jose`` - - .. note:: - - The native-python backend cannot process certificates. - - #. pycrypto - - * This backend uses `pycrypto`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycrypto]`` - * Unused dependencies: - - * ``rsa`` - - .. warning:: - - The `pycrypto`_ project has not been maintained since 2013. - This backend is maintained for legacy compatibility purposes only. - Do not use this backend unless you cannot use any of the others. - - Usage - ----- - - .. code-block:: python - - >>> from jose import jwt - >>> token = jwt.encode({'key': 'value'}, 'secret', algorithm='HS256') - u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.FG-8UppwHaFp1LgRYQQeS6EDQF7_6-bMFegNucHjmWg' - - >>> jwt.decode(token, 'secret', algorithms=['HS256']) - {u'key': u'value'} - - - Thanks - ------ - - This library was originally based heavily on the work of the folks over at PyJWT_. - - .. |Build Status| image:: https://travis-ci.org/mpdavis/python-jose.svg?branch=master - :target: https://travis-ci.org/mpdavis/python-jose - .. |Coverage Status| image:: http://codecov.io/github/mpdavis/python-jose/coverage.svg?branch=master - :target: http://codecov.io/github/mpdavis/python-jose?branch=master - .. |Docs| image:: https://readthedocs.org/projects/python-jose/badge/ - :target: https://python-jose.readthedocs.org/en/latest/ - .. _ReadTheDocs: https://python-jose.readthedocs.org/en/latest/ - .. _PyJWT: https://github.com/jpadilla/pyjwt - .. _pyca/cryptography: http://cryptography.io/ - .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/ - .. _pycrypto: https://www.dlitz.net/software/pycrypto/ - .. _python-ecdsa: https://github.com/warner/python-ecdsa - .. _python-rsa: https://stuvel.eu/rsa - -Keywords: jose jws jwe jwt json web token security signing -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Natural Language :: English -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Utilities diff --git a/README.rst b/README.rst index d5dcc40..cfdaa1e 100644 --- a/README.rst +++ b/README.rst @@ -1,113 +1,106 @@ python-jose =========== A JOSE implementation in Python -|Build Status| |Coverage Status| |Docs| +|pypi| |Github Actions CI Status| |Coverage Status| |Docs| |style| Docs are available on ReadTheDocs_. The JavaScript Object Signing and Encryption (JOSE) technologies - JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Algorithms (JWA) - collectively can be used to encrypt and/or sign content using a variety of algorithms. While the full set of permutations is extremely large, and might be daunting to some, it is expected that most applications will only use a small set of algorithms to meet their needs. Installation ------------ :: $ pip install python-jose[cryptography] Cryptographic Backends ---------------------- -As of 3.1.0, python-jose implements four different cryptographic backends. +As of 3.3.0, python-jose implements three different cryptographic backends. The backend must be selected as an extra when installing python-jose. If you do not select a backend, the native-python backend will be installed. Unless otherwise noted, all backends support all operations. Due to complexities with setuptools, the native-python backend is always installed, even if you select a different backend on install. We recommend that you remove unnecessary dependencies in production. #. cryptography * This backend uses `pyca/cryptography`_ for all cryptographic operations. This is the recommended backend and is selected over all other backends if any others are present. * Installation: ``pip install python-jose[cryptography]`` * Unused dependencies: * ``rsa`` * ``ecdsa`` * ``pyasn1`` #. pycryptodome * This backend uses `pycryptodome`_ for all cryptographic operations. * Installation: ``pip install python-jose[pycryptodome]`` * Unused dependencies: * ``rsa`` #. native-python * This backend uses `python-rsa`_ and `python-ecdsa`_ for all cryptographic operations. This backend is always installed but any other backend will take precedence if one is installed. * Installation: ``pip install python-jose`` .. note:: The native-python backend cannot process certificates. -#. pycrypto - - * This backend uses `pycrypto`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycrypto]`` - * Unused dependencies: - - * ``rsa`` - - .. warning:: - - The `pycrypto`_ project has not been maintained since 2013. - This backend is maintained for legacy compatibility purposes only. - Do not use this backend unless you cannot use any of the others. - Usage ----- .. code-block:: python >>> from jose import jwt >>> token = jwt.encode({'key': 'value'}, 'secret', algorithm='HS256') u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.FG-8UppwHaFp1LgRYQQeS6EDQF7_6-bMFegNucHjmWg' >>> jwt.decode(token, 'secret', algorithms=['HS256']) {u'key': u'value'} Thanks ------ This library was originally based heavily on the work of the folks over at PyJWT_. -.. |Build Status| image:: https://travis-ci.org/mpdavis/python-jose.svg?branch=master - :target: https://travis-ci.org/mpdavis/python-jose +.. |pypi| image:: https://img.shields.io/pypi/v/python-jose?style=flat-square + :target: https://pypi.org/project/python-jose/ + :alt: PyPI +.. |Github Actions CI Status| image:: https://github.com/mpdavis/python-jose/workflows/main/badge.svg?branch=master + :target: https://github.com/mpdavis/python-jose/actions?workflow=main + :alt: Github Actions CI Status .. |Coverage Status| image:: http://codecov.io/github/mpdavis/python-jose/coverage.svg?branch=master :target: http://codecov.io/github/mpdavis/python-jose?branch=master .. |Docs| image:: https://readthedocs.org/projects/python-jose/badge/ :target: https://python-jose.readthedocs.org/en/latest/ .. _ReadTheDocs: https://python-jose.readthedocs.org/en/latest/ .. _PyJWT: https://github.com/jpadilla/pyjwt .. _pyca/cryptography: http://cryptography.io/ .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/ .. _pycrypto: https://www.dlitz.net/software/pycrypto/ .. _python-ecdsa: https://github.com/warner/python-ecdsa .. _python-rsa: https://stuvel.eu/rsa +.. |style| image:: https://img.shields.io/badge/code%20style-black-000000.svg + :target: https://github.com/psf/black + :alt: Code style: black diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..dd643a1 --- /dev/null +++ b/TODO.md @@ -0,0 +1,19 @@ +# TODO + +* Gracefully fail if ECDSA is used with ecdsa installed +* Refactor JWS json decode and load +* Implement RSA PSS signing +* Implement JWE +* Implement JWK +* Update HTML docs with extended examples +* Add tox runner for testing against multiple versions of python + + +## Done + +* Refactor Algorithm logic with set Exceptions to return +* Add HTML documentation +* Implement ECSDA signing +* Refactor JWT claims verifcation +* Add actual exceptions instead of using the base exception +* Audit JWT claims tests and rectify against the spec diff --git a/VERSIONING.rst b/VERSIONING.rst new file mode 100644 index 0000000..a6bc99a --- /dev/null +++ b/VERSIONING.rst @@ -0,0 +1,7 @@ +---------- +Versioning +---------- + +Python-jose releases follow `Semantic Versioning`_. + +.. _Semantic Versioning: https://semver.org/ diff --git a/docs/conf.py b/docs/conf.py index 6a07674..61f4879 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,267 +1,261 @@ -# -*- coding: utf-8 -*- -# # python-jose documentation build configuration file, created by # sphinx-quickstart on Thu May 7 13:48:43 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(os.path.join(os.path.dirname(__file__), '../jose')) -sys.path.append(os.path.join(os.path.dirname(__file__), '../jose/jws')) -sys.path.append(os.path.join(os.path.dirname(__file__), '../jose/jwt')) +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../jose")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../jose/jws")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../jose/jwt")) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinxcontrib.napoleon' -] +extensions = ["sphinx.ext.autodoc", "sphinx.ext.coverage", "sphinx.ext.napoleon"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'python-jose' -copyright = u'2015, Michael Davis' +project = "python-jose" +copyright = "2015, Michael Davis" # 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 = '0.2' +version = "0.2" # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = "0.2.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +# default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- 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 = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'python-josedoc' +htmlhelp_basename = "python-josedoc" # -- 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': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # 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 = [ - ('index', 'python-jose.tex', u'python-jose Documentation', - u'Michael Davis', 'manual'), + ("index", "python-jose.tex", "python-jose Documentation", "Michael Davis", "manual"), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'python-jose', u'python-jose Documentation', - [u'Michael Davis'], 1) -] +man_pages = [("index", "python-jose", "python-jose Documentation", ["Michael Davis"], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'python-jose', u'python-jose Documentation', - u'Michael Davis', 'python-jose', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "python-jose", + "python-jose Documentation", + "Michael Davis", + "python-jose", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/index.rst b/docs/index.rst index 63cd390..9a1aa67 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,55 +1,57 @@ python-jose =========== A JOSE implementation in Python |Build Status| |Coverage Status| The JavaScript Object Signing and Encryption (JOSE) technologies - JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Algorithms (JWA) - collectively can be used to encrypt and/or sign content using a variety of algorithms. While the full set of permutations is extremely large, and might be daunting to some, it is expected that most applications will only use a small set of algorithms to meet their needs. Contents -------- .. toctree:: :maxdepth: 2 jws/index jwt/index jwk/index + jwe/index APIs ---- .. toctree:: :maxdepth: 2 jws/api jwt/api jwk/api + jwe/api Principles ---------- This is a JOSE implementation that is fully compatible with Google App Engine which requires the use of the PyCrypto library. Thanks ------ This library was originally based heavily on the work of the guys over at PyJWT_. .. |Build Status| image:: https://travis-ci.org/mpdavis/python-jose.svg?branch=master :target: https://travis-ci.org/mpdavis/python-jose .. |Coverage Status| image:: http://codecov.io/github/mpdavis/python-jose/coverage.svg?branch=master :target: http://codecov.io/github/mpdavis/python-jose?branch=master .. _PyJWT: https://github.com/jpadilla/pyjwt diff --git a/docs/jwe/api.rst b/docs/jwe/api.rst new file mode 100644 index 0000000..4086fd8 --- /dev/null +++ b/docs/jwe/api.rst @@ -0,0 +1,6 @@ + +JWE API +^^^^^^^ + +.. automodule:: jose.jwe + :members: \ No newline at end of file diff --git a/docs/jwe/index.rst b/docs/jwe/index.rst new file mode 100644 index 0000000..0a4472f --- /dev/null +++ b/docs/jwe/index.rst @@ -0,0 +1,71 @@ +JSON Web Encryption +=================== + +JSON Web Encryption (JWE) are used to encrypt a payload and represent it as a +compact URL-safe string. + +Supported Content Encryption Algorithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following algorithms are currently supported. + ++------------------+------------------------------------------------+ +| Encryption Value | Encryption Algorithm, Mode, and Auth Tag | ++==================+================================================+ +| A128CBC_HS256 | AES w/128 bit key in CBC mode w/SHA256 HMAC | ++------------------+------------------------------------------------+ +| A192CBC_HS384 | AES w/128 bit key in CBC mode w/SHA256 HMAC | ++------------------+------------------------------------------------+ +| A256CBC_HS512 | AES w/128 bit key in CBC mode w/SHA256 HMAC | ++------------------+------------------------------------------------+ +| A128GCM | AES w/128 bit key in GCM mode and GCM auth tag | ++------------------+------------------------------------------------+ +| A192GCM | AES w/192 bit key in GCM mode and GCM auth tag | ++------------------+------------------------------------------------+ +| A256GCM | AES w/256 bit key in GCM mode and GCM auth tag | ++------------------+------------------------------------------------+ + +Supported Key Management Algorithms +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following algorithms are currently supported. + ++-----------------+------------------------------------------------+ +| Algorithm Value | Key Wrap Algorithm | ++=================+================================================+ +| DIR | Direct (no key wrap) | ++-----------------+------------------------------------------------+ +| RSA1_5 | RSAES with PKCS1 v1.5 | ++-----------------+------------------------------------------------+ +| RSA_OAEP | RSAES OAEP using default parameters | ++-----------------+------------------------------------------------+ +| RSA_OAEP_256 | RSAES OAEP using SHA-256 and MGF1 with SHA-256 | ++-----------------+------------------------------------------------+ +| A128KW | AES Key Wrap with default IV using 128-bit key | ++-----------------+------------------------------------------------+ +| A192KW m | AES Key Wrap with default IV using 192-bit key | ++-----------------+------------------------------------------------+ +| A256KW | AES Key Wrap with default IV using 256-bit key | ++-----------------+------------------------------------------------+ + +Examples +^^^^^^^^ + +Encrypting Payloads +------------------- + +.. code:: python + + >>> from jose import jwe + >>> jwe.encrypt('Hello, World!', 'asecret128bitkey', algorithm='dir', encryption='A128GCM') + 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..McILMB3dYsNJSuhcDzQshA.OfX9H_mcUpHDeRM4IA.CcnTWqaqxNsjT4eCaUABSg' + + +Decrypting Payloads +-------------------------- + +.. code:: python + + >>> from jose import jwe + >>> jwe.decrypt('eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..McILMB3dYsNJSuhcDzQshA.OfX9H_mcUpHDeRM4IA.CcnTWqaqxNsjT4eCaUABSg', 'asecret128bitkey') + 'Hello, World!' diff --git a/jose/__init__.py b/jose/__init__.py index ddf2dcb..054baa7 100644 --- a/jose/__init__.py +++ b/jose/__init__.py @@ -1,11 +1,10 @@ - -__version__ = "3.1.0" -__author__ = 'Michael Davis' -__license__ = 'MIT' -__copyright__ = 'Copyright 2016 Michael Davis' +__version__ = "3.3.0" +__author__ = "Michael Davis" +__license__ = "MIT" +__copyright__ = "Copyright 2016 Michael Davis" +from .exceptions import ExpiredSignatureError # noqa: F401 from .exceptions import JOSEError # noqa: F401 from .exceptions import JWSError # noqa: F401 -from .exceptions import ExpiredSignatureError # noqa: F401 from .exceptions import JWTError # noqa: F401 diff --git a/jose/backends/__init__.py b/jose/backends/__init__.py index d1b9fa1..e7bba69 100644 --- a/jose/backends/__init__.py +++ b/jose/backends/__init__.py @@ -1,13 +1,32 @@ +try: + from jose.backends.cryptography_backend import get_random_bytes # noqa: F401 +except ImportError: + try: + from jose.backends.pycrypto_backend import get_random_bytes # noqa: F401 + except ImportError: + from jose.backends.native import get_random_bytes # noqa: F401 try: from jose.backends.cryptography_backend import CryptographyRSAKey as RSAKey # noqa: F401 except ImportError: try: - from jose.backends.pycrypto_backend import RSAKey # noqa: F401 - except ImportError: from jose.backends.rsa_backend import RSAKey # noqa: F401 + except ImportError: + RSAKey = None try: from jose.backends.cryptography_backend import CryptographyECKey as ECKey # noqa: F401 except ImportError: from jose.backends.ecdsa_backend import ECDSAECKey as ECKey # noqa: F401 + +try: + from jose.backends.cryptography_backend import CryptographyAESKey as AESKey # noqa: F401 +except ImportError: + AESKey = None + +try: + from jose.backends.cryptography_backend import CryptographyHMACKey as HMACKey # noqa: F401 +except ImportError: + from jose.backends.native import HMACKey # noqa: F401 + +from .base import DIRKey # noqa: F401 diff --git a/jose/backends/_asn1.py b/jose/backends/_asn1.py index e252cc7..af5fa8b 100644 --- a/jose/backends/_asn1.py +++ b/jose/backends/_asn1.py @@ -1,82 +1,83 @@ """ASN1 encoding helpers for converting between PKCS1 and PKCS8. -Required by rsa_backend and pycrypto_backend but not cryptography_backend. +Required by rsa_backend but not cryptography_backend. """ from pyasn1.codec.der import decoder, encoder from pyasn1.type import namedtype, univ RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" class RsaAlgorithmIdentifier(univ.Sequence): """ASN1 structure for recording RSA PrivateKeyAlgorithm identifiers.""" + componentType = namedtype.NamedTypes( - namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), - namedtype.NamedType("parameters", univ.Null()) + namedtype.NamedType("rsaEncryption", univ.ObjectIdentifier()), namedtype.NamedType("parameters", univ.Null()) ) class PKCS8PrivateKey(univ.Sequence): """ASN1 structure for recording PKCS8 private keys.""" + componentType = namedtype.NamedTypes( namedtype.NamedType("version", univ.Integer()), namedtype.NamedType("privateKeyAlgorithm", RsaAlgorithmIdentifier()), - namedtype.NamedType("privateKey", univ.OctetString()) + namedtype.NamedType("privateKey", univ.OctetString()), ) class PublicKeyInfo(univ.Sequence): """ASN1 structure for recording PKCS8 public keys.""" + componentType = namedtype.NamedTypes( - namedtype.NamedType("algorithm", RsaAlgorithmIdentifier()), - namedtype.NamedType("publicKey", univ.BitString()) + namedtype.NamedType("algorithm", RsaAlgorithmIdentifier()), namedtype.NamedType("publicKey", univ.BitString()) ) def rsa_private_key_pkcs8_to_pkcs1(pkcs8_key): """Convert a PKCS8-encoded RSA private key to PKCS1.""" decoded_values = decoder.decode(pkcs8_key, asn1Spec=PKCS8PrivateKey()) try: decoded_key = decoded_values[0] except IndexError: raise ValueError("Invalid private key encoding") return decoded_key["privateKey"] def rsa_private_key_pkcs1_to_pkcs8(pkcs1_key): """Convert a PKCS1-encoded RSA private key to PKCS8.""" algorithm = RsaAlgorithmIdentifier() algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID pkcs8_key = PKCS8PrivateKey() pkcs8_key["version"] = 0 pkcs8_key["privateKeyAlgorithm"] = algorithm pkcs8_key["privateKey"] = pkcs1_key return encoder.encode(pkcs8_key) def rsa_public_key_pkcs1_to_pkcs8(pkcs1_key): """Convert a PKCS1-encoded RSA private key to PKCS8.""" algorithm = RsaAlgorithmIdentifier() algorithm["rsaEncryption"] = RSA_ENCRYPTION_ASN1_OID pkcs8_key = PublicKeyInfo() pkcs8_key["algorithm"] = algorithm pkcs8_key["publicKey"] = univ.BitString.fromOctetString(pkcs1_key) return encoder.encode(pkcs8_key) def rsa_public_key_pkcs8_to_pkcs1(pkcs8_key): """Convert a PKCS8-encoded RSA private key to PKCS1.""" decoded_values = decoder.decode(pkcs8_key, asn1Spec=PublicKeyInfo()) try: decoded_key = decoded_values[0] except IndexError: raise ValueError("Invalid public key encoding.") return decoded_key["publicKey"].asOctets() diff --git a/jose/backends/base.py b/jose/backends/base.py index 37fc2ea..b000a52 100644 --- a/jose/backends/base.py +++ b/jose/backends/base.py @@ -1,21 +1,89 @@ -class Key(object): +from ..utils import base64url_encode, ensure_binary + + +class Key: """ A simple interface for implementing JWK keys. """ + def __init__(self, key, algorithm): pass def sign(self, msg): raise NotImplementedError() def verify(self, msg, sig): raise NotImplementedError() def public_key(self): raise NotImplementedError() def to_pem(self): raise NotImplementedError() def to_dict(self): raise NotImplementedError() + + def encrypt(self, plain_text, aad=None): + """ + Encrypt the plain text and generate an auth tag if appropriate + + Args: + plain_text (bytes): Data to encrypt + aad (bytes, optional): Authenticated Additional Data if key's algorithm supports auth mode + + Returns: + (bytes, bytes, bytes): IV, cipher text, and auth tag + """ + raise NotImplementedError() + + def decrypt(self, cipher_text, iv=None, aad=None, tag=None): + """ + Decrypt the cipher text and validate the auth tag if present + Args: + cipher_text (bytes): Cipher text to decrypt + iv (bytes): IV if block mode + aad (bytes): Additional Authenticated Data to verify if auth mode + tag (bytes): Authentication tag if auth mode + + Returns: + bytes: Decrypted value + """ + raise NotImplementedError() + + def wrap_key(self, key_data): + """ + Wrap the the plain text key data + + Args: + key_data (bytes): Key data to wrap + + Returns: + bytes: Wrapped key + """ + raise NotImplementedError() + + def unwrap_key(self, wrapped_key): + """ + Unwrap the the wrapped key data + + Args: + wrapped_key (bytes): Wrapped key data to unwrap + + Returns: + bytes: Unwrapped key + """ + raise NotImplementedError() + + +class DIRKey(Key): + def __init__(self, key_data, algorithm): + self._key = ensure_binary(key_data) + self._alg = algorithm + + def to_dict(self): + return { + "alg": self._alg, + "kty": "oct", + "k": base64url_encode(self._key), + } diff --git a/jose/backends/cryptography_backend.py b/jose/backends/cryptography_backend.py index b9bdc0d..abd2426 100644 --- a/jose/backends/cryptography_backend.py +++ b/jose/backends/cryptography_backend.py @@ -1,371 +1,605 @@ -from __future__ import division - import math +import warnings -import six - -try: - from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey -except ImportError: - EcdsaSigningKey = EcdsaVerifyingKey = None - -from jose.backends.base import Key -from jose.utils import base64_to_long, long_to_base64 -from jose.constants import ALGORITHMS -from jose.exceptions import JWKError - -from cryptography.exceptions import InvalidSignature +from cryptography.exceptions import InvalidSignature, InvalidTag from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes, serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding +from cryptography.hazmat.bindings.openssl.binding import Binding +from cryptography.hazmat.primitives import hashes, hmac, serialization +from cryptography.hazmat.primitives.asymmetric import ec, padding, rsa from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature +from cryptography.hazmat.primitives.ciphers import Cipher, aead, algorithms, modes +from cryptography.hazmat.primitives.keywrap import InvalidUnwrap, aes_key_unwrap, aes_key_wrap +from cryptography.hazmat.primitives.padding import PKCS7 from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key -from cryptography.utils import int_from_bytes, int_to_bytes +from cryptography.utils import int_to_bytes from cryptography.x509 import load_pem_x509_certificate +from ..constants import ALGORITHMS +from ..exceptions import JWEError, JWKError +from ..utils import base64_to_long, base64url_decode, base64url_encode, ensure_binary, long_to_base64 +from .base import Key + +_binding = None + + +def get_random_bytes(num_bytes): + """ + Get random bytes + + Currently, Cryptography returns OS random bytes. If you want OpenSSL + generated random bytes, you'll have to switch the RAND engine after + initializing the OpenSSL backend + Args: + num_bytes (int): Number of random bytes to generate and return + Returns: + bytes: Random bytes + """ + global _binding + + if _binding is None: + _binding = Binding() + + buf = _binding.ffi.new("char[]", num_bytes) + _binding.lib.RAND_bytes(buf, num_bytes) + rand_bytes = _binding.ffi.buffer(buf, num_bytes)[:] + return rand_bytes + class CryptographyECKey(Key): SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, key, algorithm, cryptography_backend=default_backend): if algorithm not in ALGORITHMS.EC: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) self.hash_alg = { ALGORITHMS.ES256: self.SHA256, ALGORITHMS.ES384: self.SHA384, - ALGORITHMS.ES512: self.SHA512 + ALGORITHMS.ES512: self.SHA512, }.get(algorithm) self._algorithm = algorithm self.cryptography_backend = cryptography_backend - if hasattr(key, 'public_bytes') or hasattr(key, 'private_bytes'): + if hasattr(key, "public_bytes") or hasattr(key, "private_bytes"): self.prepared_key = key return - if None not in (EcdsaSigningKey, EcdsaVerifyingKey) and isinstance(key, (EcdsaSigningKey, EcdsaVerifyingKey)): + if hasattr(key, "to_pem"): # convert to PEM and let cryptography below load it as PEM - key = key.to_pem().decode('utf-8') + key = key.to_pem().decode("utf-8") if isinstance(key, dict): self.prepared_key = self._process_jwk(key) return - if isinstance(key, six.string_types): - key = key.encode('utf-8') + if isinstance(key, str): + key = key.encode("utf-8") - if isinstance(key, six.binary_type): + if isinstance(key, bytes): # Attempt to load key. We don't know if it's # a Public Key or a Private Key, so we try # the Public Key first. try: try: key = load_pem_public_key(key, self.cryptography_backend()) except ValueError: key = load_pem_private_key(key, password=None, backend=self.cryptography_backend()) except Exception as e: raise JWKError(e) self.prepared_key = key return - raise JWKError('Unable to parse an ECKey from key: %s' % key) + raise JWKError("Unable to parse an ECKey from key: %s" % key) def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'EC': - raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get('kty')) + if not jwk_dict.get("kty") == "EC": + raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get("kty")) - if not all(k in jwk_dict for k in ['x', 'y', 'crv']): - raise JWKError('Mandatory parameters are missing') + if not all(k in jwk_dict for k in ["x", "y", "crv"]): + raise JWKError("Mandatory parameters are missing") - x = base64_to_long(jwk_dict.get('x')) - y = base64_to_long(jwk_dict.get('y')) + x = base64_to_long(jwk_dict.get("x")) + y = base64_to_long(jwk_dict.get("y")) curve = { - 'P-256': ec.SECP256R1, - 'P-384': ec.SECP384R1, - 'P-521': ec.SECP521R1, - }[jwk_dict['crv']] + "P-256": ec.SECP256R1, + "P-384": ec.SECP384R1, + "P-521": ec.SECP521R1, + }[jwk_dict["crv"]] public = ec.EllipticCurvePublicNumbers(x, y, curve()) - if 'd' in jwk_dict: - d = base64_to_long(jwk_dict.get('d')) + if "d" in jwk_dict: + d = base64_to_long(jwk_dict.get("d")) private = ec.EllipticCurvePrivateNumbers(d, public) return private.private_key(self.cryptography_backend()) else: return public.public_key(self.cryptography_backend()) def _sig_component_length(self): """Determine the correct serialization length for an encoded signature component. This is the number of bytes required to encode the maximum key value. """ return int(math.ceil(self.prepared_key.key_size / 8.0)) def _der_to_raw(self, der_signature): """Convert signature from DER encoding to RAW encoding.""" r, s = decode_dss_signature(der_signature) component_length = self._sig_component_length() return int_to_bytes(r, component_length) + int_to_bytes(s, component_length) def _raw_to_der(self, raw_signature): """Convert signature from RAW encoding to DER encoding.""" component_length = self._sig_component_length() if len(raw_signature) != int(2 * component_length): raise ValueError("Invalid signature") r_bytes = raw_signature[:component_length] s_bytes = raw_signature[component_length:] - r = int_from_bytes(r_bytes, "big") - s = int_from_bytes(s_bytes, "big") + r = int.from_bytes(r_bytes, "big") + s = int.from_bytes(s_bytes, "big") return encode_dss_signature(r, s) def sign(self, msg): if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size: - raise TypeError("this curve (%s) is too short " - "for your digest (%d)" % (self.prepared_key.curve.name, - 8 * self.hash_alg.digest_size)) + raise TypeError( + "this curve (%s) is too short " + "for your digest (%d)" % (self.prepared_key.curve.name, 8 * self.hash_alg.digest_size) + ) signature = self.prepared_key.sign(msg, ec.ECDSA(self.hash_alg())) return self._der_to_raw(signature) def verify(self, msg, sig): try: signature = self._raw_to_der(sig) self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg())) return True except Exception: return False def is_public(self): - return hasattr(self.prepared_key, 'public_bytes') + return hasattr(self.prepared_key, "public_bytes") def public_key(self): if self.is_public(): return self return self.__class__(self.prepared_key.public_key(), self._algorithm) def to_pem(self): if self.is_public(): pem = self.prepared_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo + encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo ) return pem pem = self.prepared_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption() + encryption_algorithm=serialization.NoEncryption(), ) return pem def to_dict(self): if not self.is_public(): public_key = self.prepared_key.public_key() else: public_key = self.prepared_key crv = { - 'secp256r1': 'P-256', - 'secp384r1': 'P-384', - 'secp521r1': 'P-521', + "secp256r1": "P-256", + "secp384r1": "P-384", + "secp521r1": "P-521", }[self.prepared_key.curve.name] # Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of # RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve # points must be encoded as octed-strings of this length. key_size = (self.prepared_key.curve.key_size + 7) // 8 data = { - 'alg': self._algorithm, - 'kty': 'EC', - 'crv': crv, - 'x': long_to_base64(public_key.public_numbers().x, size=key_size), - 'y': long_to_base64(public_key.public_numbers().y, size=key_size), + "alg": self._algorithm, + "kty": "EC", + "crv": crv, + "x": long_to_base64(public_key.public_numbers().x, size=key_size).decode("ASCII"), + "y": long_to_base64(public_key.public_numbers().y, size=key_size).decode("ASCII"), } if not self.is_public(): - data['d'] = long_to_base64( - self.prepared_key.private_numbers().private_value, - size=key_size - ) + private_value = self.prepared_key.private_numbers().private_value + data["d"] = long_to_base64(private_value, size=key_size).decode("ASCII") return data class CryptographyRSAKey(Key): SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 + RSA1_5 = padding.PKCS1v15() + RSA_OAEP = padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None) + RSA_OAEP_256 = padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None) + def __init__(self, key, algorithm, cryptography_backend=default_backend): if algorithm not in ALGORITHMS.RSA: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) self.hash_alg = { ALGORITHMS.RS256: self.SHA256, ALGORITHMS.RS384: self.SHA384, - ALGORITHMS.RS512: self.SHA512 + ALGORITHMS.RS512: self.SHA512, }.get(algorithm) self._algorithm = algorithm + self.padding = { + ALGORITHMS.RSA1_5: self.RSA1_5, + ALGORITHMS.RSA_OAEP: self.RSA_OAEP, + ALGORITHMS.RSA_OAEP_256: self.RSA_OAEP_256, + }.get(algorithm) + self.cryptography_backend = cryptography_backend # if it conforms to RSAPublicKey interface - if hasattr(key, 'public_bytes') and hasattr(key, 'public_numbers'): + if hasattr(key, "public_bytes") and hasattr(key, "public_numbers"): self.prepared_key = key return if isinstance(key, dict): self.prepared_key = self._process_jwk(key) return - if isinstance(key, six.string_types): - key = key.encode('utf-8') + if isinstance(key, str): + key = key.encode("utf-8") - if isinstance(key, six.binary_type): + if isinstance(key, bytes): try: - if key.startswith(b'-----BEGIN CERTIFICATE-----'): + if key.startswith(b"-----BEGIN CERTIFICATE-----"): self._process_cert(key) return try: self.prepared_key = load_pem_public_key(key, self.cryptography_backend()) except ValueError: self.prepared_key = load_pem_private_key(key, password=None, backend=self.cryptography_backend()) except Exception as e: raise JWKError(e) return - raise JWKError('Unable to parse an RSA_JWK from key: %s' % key) + raise JWKError("Unable to parse an RSA_JWK from key: %s" % key) def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'RSA': - raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty')) + if not jwk_dict.get("kty") == "RSA": + raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get("kty")) - e = base64_to_long(jwk_dict.get('e', 256)) - n = base64_to_long(jwk_dict.get('n')) + e = base64_to_long(jwk_dict.get("e", 256)) + n = base64_to_long(jwk_dict.get("n")) public = rsa.RSAPublicNumbers(e, n) - if 'd' not in jwk_dict: + if "d" not in jwk_dict: return public.public_key(self.cryptography_backend()) else: # This is a private key. - d = base64_to_long(jwk_dict.get('d')) + d = base64_to_long(jwk_dict.get("d")) - extra_params = ['p', 'q', 'dp', 'dq', 'qi'] + extra_params = ["p", "q", "dp", "dq", "qi"] if any(k in jwk_dict for k in extra_params): # Precomputed private key parameters are available. if not all(k in jwk_dict for k in extra_params): # These values must be present when 'p' is according to # Section 6.3.2 of RFC7518, so if they are not we raise # an error. - raise JWKError('Precomputed private key parameters are incomplete.') + raise JWKError("Precomputed private key parameters are incomplete.") - p = base64_to_long(jwk_dict['p']) - q = base64_to_long(jwk_dict['q']) - dp = base64_to_long(jwk_dict['dp']) - dq = base64_to_long(jwk_dict['dq']) - qi = base64_to_long(jwk_dict['qi']) + p = base64_to_long(jwk_dict["p"]) + q = base64_to_long(jwk_dict["q"]) + dp = base64_to_long(jwk_dict["dp"]) + dq = base64_to_long(jwk_dict["dq"]) + qi = base64_to_long(jwk_dict["qi"]) else: # The precomputed private key parameters are not available, # so we use cryptography's API to fill them in. 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) private = rsa.RSAPrivateNumbers(p, q, d, dp, dq, qi, public) return private.private_key(self.cryptography_backend()) def _process_cert(self, key): key = load_pem_x509_certificate(key, self.cryptography_backend()) self.prepared_key = key.public_key() def sign(self, msg): try: - signature = self.prepared_key.sign( - msg, - padding.PKCS1v15(), - self.hash_alg() - ) + signature = self.prepared_key.sign(msg, padding.PKCS1v15(), self.hash_alg()) except Exception as e: raise JWKError(e) return signature def verify(self, msg, sig): + if not self.is_public(): + warnings.warn("Attempting to verify a message with a private key. " "This is not recommended.") + try: - self.prepared_key.verify( - sig, - msg, - padding.PKCS1v15(), - self.hash_alg() - ) + self.public_key().prepared_key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) return True except InvalidSignature: return False def is_public(self): - return hasattr(self.prepared_key, 'public_bytes') + return hasattr(self.prepared_key, "public_bytes") def public_key(self): if self.is_public(): return self return self.__class__(self.prepared_key.public_key(), self._algorithm) - def to_pem(self, pem_format='PKCS8'): + def to_pem(self, pem_format="PKCS8"): if self.is_public(): - if pem_format == 'PKCS8': + if pem_format == "PKCS8": fmt = serialization.PublicFormat.SubjectPublicKeyInfo - elif pem_format == 'PKCS1': + elif pem_format == "PKCS1": fmt = serialization.PublicFormat.PKCS1 else: raise ValueError("Invalid format specified: %r" % pem_format) - pem = self.prepared_key.public_bytes( - encoding=serialization.Encoding.PEM, - format=fmt - ) + pem = self.prepared_key.public_bytes(encoding=serialization.Encoding.PEM, format=fmt) return pem - if pem_format == 'PKCS8': + if pem_format == "PKCS8": fmt = serialization.PrivateFormat.PKCS8 - elif pem_format == 'PKCS1': + elif pem_format == "PKCS1": fmt = serialization.PrivateFormat.TraditionalOpenSSL else: raise ValueError("Invalid format specified: %r" % pem_format) return self.prepared_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=fmt, - encryption_algorithm=serialization.NoEncryption() + encoding=serialization.Encoding.PEM, format=fmt, encryption_algorithm=serialization.NoEncryption() ) def to_dict(self): if not self.is_public(): public_key = self.prepared_key.public_key() else: public_key = self.prepared_key data = { - 'alg': self._algorithm, - 'kty': 'RSA', - 'n': long_to_base64(public_key.public_numbers().n), - 'e': long_to_base64(public_key.public_numbers().e), + "alg": self._algorithm, + "kty": "RSA", + "n": long_to_base64(public_key.public_numbers().n).decode("ASCII"), + "e": long_to_base64(public_key.public_numbers().e).decode("ASCII"), } if not self.is_public(): - data.update({ - 'd': long_to_base64(self.prepared_key.private_numbers().d), - 'p': long_to_base64(self.prepared_key.private_numbers().p), - 'q': long_to_base64(self.prepared_key.private_numbers().q), - 'dp': long_to_base64(self.prepared_key.private_numbers().dmp1), - 'dq': long_to_base64(self.prepared_key.private_numbers().dmq1), - 'qi': long_to_base64(self.prepared_key.private_numbers().iqmp), - }) + data.update( + { + "d": long_to_base64(self.prepared_key.private_numbers().d).decode("ASCII"), + "p": long_to_base64(self.prepared_key.private_numbers().p).decode("ASCII"), + "q": long_to_base64(self.prepared_key.private_numbers().q).decode("ASCII"), + "dp": long_to_base64(self.prepared_key.private_numbers().dmp1).decode("ASCII"), + "dq": long_to_base64(self.prepared_key.private_numbers().dmq1).decode("ASCII"), + "qi": long_to_base64(self.prepared_key.private_numbers().iqmp).decode("ASCII"), + } + ) + + return data + + def wrap_key(self, key_data): + try: + wrapped_key = self.prepared_key.encrypt(key_data, self.padding) + except Exception as e: + raise JWEError(e) + + return wrapped_key + def unwrap_key(self, wrapped_key): + try: + unwrapped_key = self.prepared_key.decrypt(wrapped_key, self.padding) + return unwrapped_key + except Exception as e: + raise JWEError(e) + + +class CryptographyAESKey(Key): + KEY_128 = (ALGORITHMS.A128GCM, ALGORITHMS.A128GCMKW, ALGORITHMS.A128KW, ALGORITHMS.A128CBC) + KEY_192 = (ALGORITHMS.A192GCM, ALGORITHMS.A192GCMKW, ALGORITHMS.A192KW, ALGORITHMS.A192CBC) + KEY_256 = ( + ALGORITHMS.A256GCM, + ALGORITHMS.A256GCMKW, + ALGORITHMS.A256KW, + ALGORITHMS.A128CBC_HS256, + ALGORITHMS.A256CBC, + ) + KEY_384 = (ALGORITHMS.A192CBC_HS384,) + KEY_512 = (ALGORITHMS.A256CBC_HS512,) + + AES_KW_ALGS = (ALGORITHMS.A128KW, ALGORITHMS.A192KW, ALGORITHMS.A256KW) + + MODES = { + ALGORITHMS.A128GCM: modes.GCM, + ALGORITHMS.A192GCM: modes.GCM, + ALGORITHMS.A256GCM: modes.GCM, + ALGORITHMS.A128CBC_HS256: modes.CBC, + ALGORITHMS.A192CBC_HS384: modes.CBC, + ALGORITHMS.A256CBC_HS512: modes.CBC, + ALGORITHMS.A128CBC: modes.CBC, + ALGORITHMS.A192CBC: modes.CBC, + ALGORITHMS.A256CBC: modes.CBC, + ALGORITHMS.A128GCMKW: modes.GCM, + ALGORITHMS.A192GCMKW: modes.GCM, + ALGORITHMS.A256GCMKW: modes.GCM, + ALGORITHMS.A128KW: None, + ALGORITHMS.A192KW: None, + ALGORITHMS.A256KW: None, + } + + def __init__(self, key, algorithm): + if algorithm not in ALGORITHMS.AES: + raise JWKError("%s is not a valid AES algorithm" % algorithm) + if algorithm not in ALGORITHMS.SUPPORTED.union(ALGORITHMS.AES_PSEUDO): + raise JWKError("%s is not a supported algorithm" % algorithm) + + self._algorithm = algorithm + self._mode = self.MODES.get(self._algorithm) + + if algorithm in self.KEY_128 and len(key) != 16: + raise JWKError(f"Key must be 128 bit for alg {algorithm}") + elif algorithm in self.KEY_192 and len(key) != 24: + raise JWKError(f"Key must be 192 bit for alg {algorithm}") + elif algorithm in self.KEY_256 and len(key) != 32: + raise JWKError(f"Key must be 256 bit for alg {algorithm}") + elif algorithm in self.KEY_384 and len(key) != 48: + raise JWKError(f"Key must be 384 bit for alg {algorithm}") + elif algorithm in self.KEY_512 and len(key) != 64: + raise JWKError(f"Key must be 512 bit for alg {algorithm}") + + self._key = key + + def to_dict(self): + data = {"alg": self._algorithm, "kty": "oct", "k": base64url_encode(self._key)} return data + + def encrypt(self, plain_text, aad=None): + plain_text = ensure_binary(plain_text) + try: + iv = get_random_bytes(algorithms.AES.block_size // 8) + mode = self._mode(iv) + if mode.name == "GCM": + cipher = aead.AESGCM(self._key) + cipher_text_and_tag = cipher.encrypt(iv, plain_text, aad) + cipher_text = cipher_text_and_tag[: len(cipher_text_and_tag) - 16] + auth_tag = cipher_text_and_tag[-16:] + else: + cipher = Cipher(algorithms.AES(self._key), mode, backend=default_backend()) + encryptor = cipher.encryptor() + padder = PKCS7(algorithms.AES.block_size).padder() + padded_data = padder.update(plain_text) + padded_data += padder.finalize() + cipher_text = encryptor.update(padded_data) + encryptor.finalize() + auth_tag = None + return iv, cipher_text, auth_tag + except Exception as e: + raise JWEError(e) + + def decrypt(self, cipher_text, iv=None, aad=None, tag=None): + cipher_text = ensure_binary(cipher_text) + try: + iv = ensure_binary(iv) + mode = self._mode(iv) + if mode.name == "GCM": + if tag is None: + raise ValueError("tag cannot be None") + cipher = aead.AESGCM(self._key) + cipher_text_and_tag = cipher_text + tag + try: + plain_text = cipher.decrypt(iv, cipher_text_and_tag, aad) + except InvalidTag: + raise JWEError("Invalid JWE Auth Tag") + else: + cipher = Cipher(algorithms.AES(self._key), mode, backend=default_backend()) + decryptor = cipher.decryptor() + padded_plain_text = decryptor.update(cipher_text) + padded_plain_text += decryptor.finalize() + unpadder = PKCS7(algorithms.AES.block_size).unpadder() + plain_text = unpadder.update(padded_plain_text) + plain_text += unpadder.finalize() + + return plain_text + except Exception as e: + raise JWEError(e) + + def wrap_key(self, key_data): + key_data = ensure_binary(key_data) + cipher_text = aes_key_wrap(self._key, key_data, default_backend()) + return cipher_text # IV, cipher text, auth tag + + def unwrap_key(self, wrapped_key): + wrapped_key = ensure_binary(wrapped_key) + try: + plain_text = aes_key_unwrap(self._key, wrapped_key, default_backend()) + except InvalidUnwrap as cause: + raise JWEError(cause) + return plain_text + + +class CryptographyHMACKey(Key): + """ + Performs signing and verification operations using HMAC + and the specified hash function. + """ + + ALG_MAP = {ALGORITHMS.HS256: hashes.SHA256(), ALGORITHMS.HS384: hashes.SHA384(), ALGORITHMS.HS512: hashes.SHA512()} + + def __init__(self, key, algorithm): + if algorithm not in ALGORITHMS.HMAC: + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) + self._algorithm = algorithm + self._hash_alg = self.ALG_MAP.get(algorithm) + + if isinstance(key, dict): + self.prepared_key = self._process_jwk(key) + return + + if not isinstance(key, str) and not isinstance(key, bytes): + raise JWKError("Expecting a string- or bytes-formatted key.") + + if isinstance(key, str): + key = key.encode("utf-8") + + invalid_strings = [ + b"-----BEGIN PUBLIC KEY-----", + b"-----BEGIN RSA PUBLIC KEY-----", + b"-----BEGIN CERTIFICATE-----", + b"ssh-rsa", + ] + + if any(string_value in key for string_value in invalid_strings): + raise JWKError( + "The specified key is an asymmetric key or x509 certificate and" + " should not be used as an HMAC secret." + ) + + self.prepared_key = key + + def _process_jwk(self, jwk_dict): + if not jwk_dict.get("kty") == "oct": + raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty")) + + k = jwk_dict.get("k") + k = k.encode("utf-8") + k = bytes(k) + k = base64url_decode(k) + + return k + + def to_dict(self): + return { + "alg": self._algorithm, + "kty": "oct", + "k": base64url_encode(self.prepared_key).decode("ASCII"), + } + + def sign(self, msg): + msg = ensure_binary(msg) + h = hmac.HMAC(self.prepared_key, self._hash_alg, backend=default_backend()) + h.update(msg) + signature = h.finalize() + return signature + + def verify(self, msg, sig): + msg = ensure_binary(msg) + sig = ensure_binary(sig) + h = hmac.HMAC(self.prepared_key, self._hash_alg, backend=default_backend()) + h.update(msg) + try: + h.verify(sig) + verified = True + except InvalidSignature: + verified = False + return verified diff --git a/jose/backends/ecdsa_backend.py b/jose/backends/ecdsa_backend.py index 8b8b9a2..756c7ea 100644 --- a/jose/backends/ecdsa_backend.py +++ b/jose/backends/ecdsa_backend.py @@ -1,144 +1,150 @@ import hashlib -import six -from jose.backends.base import Key import ecdsa +from jose.backends.base import Key from jose.constants import ALGORITHMS from jose.exceptions import JWKError from jose.utils import base64_to_long, long_to_base64 class ECDSAECKey(Key): """ Performs signing and verification operations using ECDSA and the specified hash function This class requires the ecdsa package to be installed. This is based off of the implementation in PyJWT 0.3.2 """ + SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 CURVE_MAP = { SHA256: ecdsa.curves.NIST256p, SHA384: ecdsa.curves.NIST384p, SHA512: ecdsa.curves.NIST521p, } + CURVE_NAMES = ( + (ecdsa.curves.NIST256p, "P-256"), + (ecdsa.curves.NIST384p, "P-384"), + (ecdsa.curves.NIST521p, "P-521"), + ) def __init__(self, key, algorithm): if algorithm not in ALGORITHMS.EC: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) self.hash_alg = { ALGORITHMS.ES256: self.SHA256, ALGORITHMS.ES384: self.SHA384, - ALGORITHMS.ES512: self.SHA512 + ALGORITHMS.ES512: self.SHA512, }.get(algorithm) self._algorithm = algorithm self.curve = self.CURVE_MAP.get(self.hash_alg) if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)): self.prepared_key = key return if isinstance(key, dict): self.prepared_key = self._process_jwk(key) return - if isinstance(key, six.string_types): - key = key.encode('utf-8') + if isinstance(key, str): + key = key.encode("utf-8") - if isinstance(key, six.binary_type): + if isinstance(key, bytes): # Attempt to load key. We don't know if it's # a Signing Key or a Verifying Key, so we try # the Verifying Key first. try: key = ecdsa.VerifyingKey.from_pem(key) except ecdsa.der.UnexpectedDER: key = ecdsa.SigningKey.from_pem(key) except Exception as e: raise JWKError(e) self.prepared_key = key return - raise JWKError('Unable to parse an ECKey from key: %s' % key) + raise JWKError("Unable to parse an ECKey from key: %s" % key) def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'EC': - raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty')) + if not jwk_dict.get("kty") == "EC": + raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get("kty")) - if not all(k in jwk_dict for k in ['x', 'y', 'crv']): - raise JWKError('Mandatory parameters are missing') + if not all(k in jwk_dict for k in ["x", "y", "crv"]): + raise JWKError("Mandatory parameters are missing") - if 'd' in jwk_dict: + if "d" in jwk_dict: # We are dealing with a private key; the secret exponent is enough # to create an ecdsa key. - d = base64_to_long(jwk_dict.get('d')) + d = base64_to_long(jwk_dict.get("d")) return ecdsa.keys.SigningKey.from_secret_exponent(d, self.curve) else: - x = base64_to_long(jwk_dict.get('x')) - y = base64_to_long(jwk_dict.get('y')) + x = base64_to_long(jwk_dict.get("x")) + y = base64_to_long(jwk_dict.get("y")) if not ecdsa.ecdsa.point_is_valid(self.curve.generator, x, y): - raise JWKError("Point: %s, %s is not a valid point" % (x, y)) + raise JWKError(f"Point: {x}, {y} is not a valid point") point = ecdsa.ellipticcurve.Point(self.curve.curve, x, y, self.curve.order) return ecdsa.keys.VerifyingKey.from_public_point(point, self.curve) def sign(self, msg): - return self.prepared_key.sign(msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string) + return self.prepared_key.sign( + msg, hashfunc=self.hash_alg, sigencode=ecdsa.util.sigencode_string, allow_truncate=False + ) def verify(self, msg, sig): try: - return self.prepared_key.verify(sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string) + return self.prepared_key.verify( + sig, msg, hashfunc=self.hash_alg, sigdecode=ecdsa.util.sigdecode_string, allow_truncate=False + ) except Exception: return False def is_public(self): return isinstance(self.prepared_key, ecdsa.VerifyingKey) def public_key(self): if self.is_public(): return self return self.__class__(self.prepared_key.get_verifying_key(), self._algorithm) def to_pem(self): return self.prepared_key.to_pem() def to_dict(self): if not self.is_public(): public_key = self.prepared_key.get_verifying_key() else: public_key = self.prepared_key - - crv = { - ecdsa.curves.NIST256p: 'P-256', - ecdsa.curves.NIST384p: 'P-384', - ecdsa.curves.NIST521p: 'P-521', - }[self.prepared_key.curve] + crv = None + for key, value in self.CURVE_NAMES: + if key == self.prepared_key.curve: + crv = value + if not crv: + raise KeyError(f"Can't match {self.prepared_key.curve}") # Calculate the key size in bytes. Section 6.2.1.2 and 6.2.1.3 of # RFC7518 prescribes that the 'x', 'y' and 'd' parameters of the curve # points must be encoded as octed-strings of this length. key_size = self.prepared_key.curve.baselen data = { - 'alg': self._algorithm, - 'kty': 'EC', - 'crv': crv, - 'x': long_to_base64(public_key.pubkey.point.x(), size=key_size), - 'y': long_to_base64(public_key.pubkey.point.y(), size=key_size), + "alg": self._algorithm, + "kty": "EC", + "crv": crv, + "x": long_to_base64(public_key.pubkey.point.x(), size=key_size).decode("ASCII"), + "y": long_to_base64(public_key.pubkey.point.y(), size=key_size).decode("ASCII"), } if not self.is_public(): - data['d'] = long_to_base64( - self.prepared_key.privkey.secret_multiplier, - size=key_size - ) + data["d"] = long_to_base64(self.prepared_key.privkey.secret_multiplier, size=key_size).decode("ASCII") return data diff --git a/jose/backends/native.py b/jose/backends/native.py new file mode 100644 index 0000000..eb3a6ae --- /dev/null +++ b/jose/backends/native.py @@ -0,0 +1,76 @@ +import hashlib +import hmac +import os + +from jose.backends.base import Key +from jose.constants import ALGORITHMS +from jose.exceptions import JWKError +from jose.utils import base64url_decode, base64url_encode + + +def get_random_bytes(num_bytes): + return bytes(os.urandom(num_bytes)) + + +class HMACKey(Key): + """ + Performs signing and verification operations using HMAC + and the specified hash function. + """ + + HASHES = {ALGORITHMS.HS256: hashlib.sha256, ALGORITHMS.HS384: hashlib.sha384, ALGORITHMS.HS512: hashlib.sha512} + + def __init__(self, key, algorithm): + if algorithm not in ALGORITHMS.HMAC: + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) + self._algorithm = algorithm + self._hash_alg = self.HASHES.get(algorithm) + + if isinstance(key, dict): + self.prepared_key = self._process_jwk(key) + return + + if not isinstance(key, str) and not isinstance(key, bytes): + raise JWKError("Expecting a string- or bytes-formatted key.") + + if isinstance(key, str): + key = key.encode("utf-8") + + invalid_strings = [ + b"-----BEGIN PUBLIC KEY-----", + b"-----BEGIN RSA PUBLIC KEY-----", + b"-----BEGIN CERTIFICATE-----", + b"ssh-rsa", + ] + + if any(string_value in key for string_value in invalid_strings): + raise JWKError( + "The specified key is an asymmetric key or x509 certificate and" + " should not be used as an HMAC secret." + ) + + self.prepared_key = key + + def _process_jwk(self, jwk_dict): + if not jwk_dict.get("kty") == "oct": + raise JWKError("Incorrect key type. Expected: 'oct', Received: %s" % jwk_dict.get("kty")) + + k = jwk_dict.get("k") + k = k.encode("utf-8") + k = bytes(k) + k = base64url_decode(k) + + return k + + def sign(self, msg): + return hmac.new(self.prepared_key, msg, self._hash_alg).digest() + + def verify(self, msg, sig): + return hmac.compare_digest(sig, self.sign(msg)) + + def to_dict(self): + return { + "alg": self._algorithm, + "kty": "oct", + "k": base64url_encode(self.prepared_key).decode("ASCII"), + } diff --git a/jose/backends/pycrypto_backend.py b/jose/backends/pycrypto_backend.py deleted file mode 100644 index a12e861..0000000 --- a/jose/backends/pycrypto_backend.py +++ /dev/null @@ -1,212 +0,0 @@ -from base64 import b64encode - -import six - -import Crypto.Hash.SHA256 -import Crypto.Hash.SHA384 -import Crypto.Hash.SHA512 - -from Crypto.PublicKey import RSA -from Crypto.Signature import PKCS1_v1_5 -from Crypto.Util.asn1 import DerSequence - -from jose.backends.base import Key -from jose.backends._asn1 import rsa_public_key_pkcs8_to_pkcs1 -from jose.utils import base64_to_long, long_to_base64 -from jose.constants import ALGORITHMS -from jose.exceptions import JWKError -from jose.utils import base64url_decode - - -# We default to using PyCryptodome, however, if PyCrypto is installed, it is -# used instead. This is so that environments that require the use of PyCrypto -# are still supported. -if hasattr(RSA, 'RsaKey'): - _RSAKey = RSA.RsaKey -else: - _RSAKey = RSA._RSAobj - - -def _der_to_pem(der_key, marker): - """ - Perform a simple DER to PEM conversion. - """ - pem_key_chunks = [('-----BEGIN %s-----' % marker).encode('utf-8')] - - # Limit base64 output lines to 64 characters by limiting input lines to 48 characters. - for chunk_start in range(0, len(der_key), 48): - pem_key_chunks.append(b64encode(der_key[chunk_start:chunk_start + 48])) - - pem_key_chunks.append(('-----END %s-----' % marker).encode('utf-8')) - - return b'\n'.join(pem_key_chunks) - - -class RSAKey(Key): - """ - Performs signing and verification operations using - RSASSA-PKCS-v1_5 and the specified hash function. - This class requires PyCrypto package to be installed. - This is based off of the implementation in PyJWT 0.3.2 - """ - - SHA256 = Crypto.Hash.SHA256 - SHA384 = Crypto.Hash.SHA384 - SHA512 = Crypto.Hash.SHA512 - - def __init__(self, key, algorithm): - - if algorithm not in ALGORITHMS.RSA: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) - - self.hash_alg = { - ALGORITHMS.RS256: self.SHA256, - ALGORITHMS.RS384: self.SHA384, - ALGORITHMS.RS512: self.SHA512 - }.get(algorithm) - self._algorithm = algorithm - - if isinstance(key, _RSAKey): - self.prepared_key = key - return - - if isinstance(key, dict): - self._process_jwk(key) - return - - if isinstance(key, six.string_types): - key = key.encode('utf-8') - - if isinstance(key, six.binary_type): - if key.startswith(b'-----BEGIN CERTIFICATE-----'): - try: - self._process_cert(key) - except Exception as e: - raise JWKError(e) - return - - try: - self.prepared_key = RSA.importKey(key) - except Exception as e: - raise JWKError(e) - return - - raise JWKError('Unable to parse an RSA_JWK from key: %s' % key) - - def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'RSA': - raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty')) - - e = base64_to_long(jwk_dict.get('e', 256)) - n = base64_to_long(jwk_dict.get('n')) - params = (n, e) - - if 'd' in jwk_dict: - params += (base64_to_long(jwk_dict.get('d')),) - - extra_params = ['p', 'q', 'dp', 'dq', 'qi'] - - if any(k in jwk_dict for k in extra_params): - # Precomputed private key parameters are available. - if not all(k in jwk_dict for k in extra_params): - # These values must be present when 'p' is according to - # Section 6.3.2 of RFC7518, so if they are not we raise - # an error. - raise JWKError('Precomputed private key parameters are incomplete.') - - p = base64_to_long(jwk_dict.get('p')) - q = base64_to_long(jwk_dict.get('q')) - qi = base64_to_long(jwk_dict.get('qi')) - - # PyCrypto does not take the dp and dq as arguments, so we do - # not pass them. Furthermore, the parameter qi specified in - # the JWK is the inverse of q modulo p, whereas PyCrypto - # takes the inverse of p modulo q. We therefore switch the - # parameters to make the third parameter the inverse of the - # second parameter modulo the first parameter. - params += (q, p, qi) - - self.prepared_key = RSA.construct(params) - - return self.prepared_key - - def _process_cert(self, key): - pemLines = key.replace(b' ', b'').split() - certDer = base64url_decode(b''.join(pemLines[1:-1])) - certSeq = DerSequence() - certSeq.decode(certDer) - tbsSeq = DerSequence() - tbsSeq.decode(certSeq[0]) - self.prepared_key = RSA.importKey(tbsSeq[6]) - return - - def sign(self, msg): - try: - return PKCS1_v1_5.new(self.prepared_key).sign(self.hash_alg.new(msg)) - except Exception as e: - raise JWKError(e) - - def verify(self, msg, sig): - try: - return PKCS1_v1_5.new(self.prepared_key).verify(self.hash_alg.new(msg), sig) - except Exception: - return False - - def is_public(self): - return not self.prepared_key.has_private() - - def public_key(self): - if self.is_public(): - return self - return self.__class__(self.prepared_key.publickey(), self._algorithm) - - def to_pem(self, pem_format='PKCS8'): - if pem_format == 'PKCS8': - pkcs = 8 - elif pem_format == 'PKCS1': - pkcs = 1 - else: - raise ValueError("Invalid pem format specified: %r" % (pem_format,)) - - if self.is_public(): - # PyCrypto/dome always export public keys as PKCS8 - if pkcs == 8: - pem = self.prepared_key.exportKey('PEM') - else: - pkcs8_der = self.prepared_key.exportKey('DER') - pkcs1_der = rsa_public_key_pkcs8_to_pkcs1(pkcs8_der) - pem = _der_to_pem(pkcs1_der, 'RSA PUBLIC KEY') - return pem - else: - pem = self.prepared_key.exportKey('PEM', pkcs=pkcs) - return pem - - def to_dict(self): - data = { - 'alg': self._algorithm, - 'kty': 'RSA', - 'n': long_to_base64(self.prepared_key.n), - 'e': long_to_base64(self.prepared_key.e), - } - - if not self.is_public(): - # Section 6.3.2 of RFC7518 prescribes that when we include the - # optional parameters p and q, we must also include the values of - # dp and dq, which are not readily available from PyCrypto - so we - # calculate them. Moreover, PyCrypto stores the inverse of p - # modulo q rather than the inverse of q modulo p, so we switch - # p and q. As far as I can tell, this is OK - RFC7518 only - # asserts that p is the 'first factor', but does not specify - # what 'first' means in this case. - dp = self.prepared_key.d % (self.prepared_key.p - 1) - dq = self.prepared_key.d % (self.prepared_key.q - 1) - data.update({ - 'd': long_to_base64(self.prepared_key.d), - 'p': long_to_base64(self.prepared_key.q), - 'q': long_to_base64(self.prepared_key.p), - 'dp': long_to_base64(dq), - 'dq': long_to_base64(dp), - 'qi': long_to_base64(self.prepared_key.u), - }) - - return data diff --git a/jose/backends/rsa_backend.py b/jose/backends/rsa_backend.py index c1f5539..4e8ccf1 100644 --- a/jose/backends/rsa_backend.py +++ b/jose/backends/rsa_backend.py @@ -1,263 +1,284 @@ import binascii - -import six -from pyasn1.error import PyAsn1Error +import warnings import rsa as pyrsa import rsa.pem as pyrsa_pem +from pyasn1.error import PyAsn1Error +from rsa import DecryptionError -from jose.backends.base import Key from jose.backends._asn1 import ( rsa_private_key_pkcs1_to_pkcs8, rsa_private_key_pkcs8_to_pkcs1, rsa_public_key_pkcs1_to_pkcs8, ) +from jose.backends.base import Key from jose.constants import ALGORITHMS -from jose.exceptions import JWKError +from jose.exceptions import JWEError, JWKError from jose.utils import base64_to_long, long_to_base64 +ALGORITHMS.SUPPORTED.remove(ALGORITHMS.RSA_OAEP) # RSA OAEP not supported LEGACY_INVALID_PKCS8_RSA_HEADER = binascii.unhexlify( "30" # sequence "8204BD" # DER-encoded sequence contents length of 1213 bytes -- INCORRECT STATIC LENGTH "020100" # integer: 0 -- Version "30" # sequence "0D" # DER-encoded sequence contents length of 13 bytes -- PrivateKeyAlgorithmIdentifier "06092A864886F70D010101" # OID -- rsaEncryption "0500" # NULL -- parameters ) ASN1_SEQUENCE_ID = binascii.unhexlify("30") RSA_ENCRYPTION_ASN1_OID = "1.2.840.113549.1.1.1" # Functions gcd and rsa_recover_prime_factors were copied from cryptography 1.9 # to enable pure python rsa module to be in compliance with section 6.3.1 of RFC7518 # which requires only private exponent (d) for private key. def _gcd(a, b): """Calculate the Greatest Common Divisor of a and b. Unless b==0, the result will have the same sign as b (so that when b is divided by it, the result comes out positive). """ while b: a, b = b, (a % b) return a # Controls the number of iterations rsa_recover_prime_factors will perform # to obtain the prime factors. Each iteration increments by 2 so the actual # maximum attempts is half this number. _MAX_RECOVERY_ATTEMPTS = 1000 def _rsa_recover_prime_factors(n, e, d): """ Compute factors p and q from the private exponent d. We assume that n has no more than two factors. This function is adapted from code in PyCrypto. """ # See 8.2.2(i) in Handbook of Applied Cryptography. ktot = d * e - 1 # The quantity d*e-1 is a multiple of phi(n), even, # and can be represented as t*2^s. t = ktot while t % 2 == 0: t = t // 2 # Cycle through all multiplicative inverses in Zn. # The algorithm is non-deterministic, but there is a 50% chance # any candidate a leads to successful factoring. # See "Digitalized Signatures and Public Key Functions as Intractable # as Factorization", M. Rabin, 1979 spotted = False a = 2 while not spotted and a < _MAX_RECOVERY_ATTEMPTS: k = t # Cycle through all values a^{t*2^i}=a^k while k < ktot: cand = pow(a, k, n) # Check if a^k is a non-trivial root of unity (mod n) if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: # We have found a number such that (cand-1)(cand+1)=0 (mod n). # Either of the terms divides n. p = _gcd(cand + 1, n) spotted = True break k *= 2 # This value was not any good... let's try another! a += 2 if not spotted: raise ValueError("Unable to compute factors p and q from exponent d.") # Found ! q, r = divmod(n, p) assert r == 0 p, q = sorted((p, q), reverse=True) return (p, q) -def pem_to_spki(pem, fmt='PKCS8'): +def pem_to_spki(pem, fmt="PKCS8"): key = RSAKey(pem, ALGORITHMS.RS256) return key.to_pem(fmt) def _legacy_private_key_pkcs8_to_pkcs1(pkcs8_key): """Legacy RSA private key PKCS8-to-PKCS1 conversion. .. warning:: This is incorrect parsing and only works because the legacy PKCS1-to-PKCS8 encoding was also incorrect. """ # Only allow this processing if the prefix matches # AND the following byte indicates an ASN1 sequence, # as we would expect with the legacy encoding. if not pkcs8_key.startswith(LEGACY_INVALID_PKCS8_RSA_HEADER + ASN1_SEQUENCE_ID): raise ValueError("Invalid private key encoding") - return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER):] + return pkcs8_key[len(LEGACY_INVALID_PKCS8_RSA_HEADER) :] class RSAKey(Key): - SHA256 = 'SHA-256' - SHA384 = 'SHA-384' - SHA512 = 'SHA-512' + SHA256 = "SHA-256" + SHA384 = "SHA-384" + SHA512 = "SHA-512" def __init__(self, key, algorithm): if algorithm not in ALGORITHMS.RSA: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) + raise JWKError("hash_alg: %s is not a valid hash algorithm" % algorithm) + + if algorithm in ALGORITHMS.RSA_KW and algorithm != ALGORITHMS.RSA1_5: + raise JWKError("alg: %s is not supported by the RSA backend" % algorithm) self.hash_alg = { ALGORITHMS.RS256: self.SHA256, ALGORITHMS.RS384: self.SHA384, - ALGORITHMS.RS512: self.SHA512 + ALGORITHMS.RS512: self.SHA512, }.get(algorithm) self._algorithm = algorithm if isinstance(key, dict): self._prepared_key = self._process_jwk(key) return if isinstance(key, (pyrsa.PublicKey, pyrsa.PrivateKey)): self._prepared_key = key return - if isinstance(key, six.string_types): - key = key.encode('utf-8') + if isinstance(key, str): + key = key.encode("utf-8") - if isinstance(key, six.binary_type): + if isinstance(key, bytes): try: self._prepared_key = pyrsa.PublicKey.load_pkcs1(key) except ValueError: try: self._prepared_key = pyrsa.PublicKey.load_pkcs1_openssl_pem(key) except ValueError: try: self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key) except ValueError: try: - der = pyrsa_pem.load_pem(key, b'PRIVATE KEY') + der = pyrsa_pem.load_pem(key, b"PRIVATE KEY") try: pkcs1_key = rsa_private_key_pkcs8_to_pkcs1(der) except PyAsn1Error: # If the key was encoded using the old, invalid, # encoding then pyasn1 will throw an error attempting # to parse the key. pkcs1_key = _legacy_private_key_pkcs8_to_pkcs1(der) self._prepared_key = pyrsa.PrivateKey.load_pkcs1(pkcs1_key, format="DER") except ValueError as e: raise JWKError(e) return - raise JWKError('Unable to parse an RSA_JWK from key: %s' % key) + raise JWKError("Unable to parse an RSA_JWK from key: %s" % key) def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'RSA': - raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty')) + if not jwk_dict.get("kty") == "RSA": + raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get("kty")) - e = base64_to_long(jwk_dict.get('e')) - n = base64_to_long(jwk_dict.get('n')) + e = base64_to_long(jwk_dict.get("e")) + n = base64_to_long(jwk_dict.get("n")) - if 'd' not in jwk_dict: + if "d" not in jwk_dict: return pyrsa.PublicKey(e=e, n=n) else: - d = base64_to_long(jwk_dict.get('d')) - extra_params = ['p', 'q', 'dp', 'dq', 'qi'] + d = base64_to_long(jwk_dict.get("d")) + extra_params = ["p", "q", "dp", "dq", "qi"] if any(k in jwk_dict for k in extra_params): # Precomputed private key parameters are available. if not all(k in jwk_dict for k in extra_params): # These values must be present when 'p' is according to # Section 6.3.2 of RFC7518, so if they are not we raise # an error. - raise JWKError('Precomputed private key parameters are incomplete.') + raise JWKError("Precomputed private key parameters are incomplete.") - p = base64_to_long(jwk_dict['p']) - q = base64_to_long(jwk_dict['q']) + p = base64_to_long(jwk_dict["p"]) + q = base64_to_long(jwk_dict["q"]) return pyrsa.PrivateKey(e=e, n=n, d=d, p=p, q=q) else: p, q = _rsa_recover_prime_factors(n, e, d) return pyrsa.PrivateKey(n=n, e=e, d=d, p=p, q=q) def sign(self, msg): return pyrsa.sign(msg, self._prepared_key, self.hash_alg) def verify(self, msg, sig): + if not self.is_public(): + warnings.warn("Attempting to verify a message with a private key. " "This is not recommended.") try: pyrsa.verify(msg, sig, self._prepared_key) return True except pyrsa.pkcs1.VerificationError: return False def is_public(self): return isinstance(self._prepared_key, pyrsa.PublicKey) def public_key(self): if isinstance(self._prepared_key, pyrsa.PublicKey): return self return self.__class__(pyrsa.PublicKey(n=self._prepared_key.n, e=self._prepared_key.e), self._algorithm) - def to_pem(self, pem_format='PKCS8'): + def to_pem(self, pem_format="PKCS8"): if isinstance(self._prepared_key, pyrsa.PrivateKey): - der = self._prepared_key.save_pkcs1(format='DER') - if pem_format == 'PKCS8': + der = self._prepared_key.save_pkcs1(format="DER") + if pem_format == "PKCS8": pkcs8_der = rsa_private_key_pkcs1_to_pkcs8(der) - pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker='PRIVATE KEY') - elif pem_format == 'PKCS1': - pem = pyrsa_pem.save_pem(der, pem_marker='RSA PRIVATE KEY') + pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PRIVATE KEY") + elif pem_format == "PKCS1": + pem = pyrsa_pem.save_pem(der, pem_marker="RSA PRIVATE KEY") else: - raise ValueError("Invalid pem format specified: %r" % (pem_format,)) + raise ValueError(f"Invalid pem format specified: {pem_format!r}") else: - if pem_format == 'PKCS8': + if pem_format == "PKCS8": pkcs1_der = self._prepared_key.save_pkcs1(format="DER") pkcs8_der = rsa_public_key_pkcs1_to_pkcs8(pkcs1_der) - pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker='PUBLIC KEY') - elif pem_format == 'PKCS1': - der = self._prepared_key.save_pkcs1(format='DER') - pem = pyrsa_pem.save_pem(der, pem_marker='RSA PUBLIC KEY') + pem = pyrsa_pem.save_pem(pkcs8_der, pem_marker="PUBLIC KEY") + elif pem_format == "PKCS1": + der = self._prepared_key.save_pkcs1(format="DER") + pem = pyrsa_pem.save_pem(der, pem_marker="RSA PUBLIC KEY") else: - raise ValueError("Invalid pem format specified: %r" % (pem_format,)) + raise ValueError(f"Invalid pem format specified: {pem_format!r}") return pem def to_dict(self): if not self.is_public(): public_key = self.public_key()._prepared_key else: public_key = self._prepared_key data = { - 'alg': self._algorithm, - 'kty': 'RSA', - 'n': long_to_base64(public_key.n), - 'e': long_to_base64(public_key.e), + "alg": self._algorithm, + "kty": "RSA", + "n": long_to_base64(public_key.n).decode("ASCII"), + "e": long_to_base64(public_key.e).decode("ASCII"), } if not self.is_public(): - data.update({ - 'd': long_to_base64(self._prepared_key.d), - 'p': long_to_base64(self._prepared_key.p), - 'q': long_to_base64(self._prepared_key.q), - 'dp': long_to_base64(self._prepared_key.exp1), - 'dq': long_to_base64(self._prepared_key.exp2), - 'qi': long_to_base64(self._prepared_key.coef), - }) + data.update( + { + "d": long_to_base64(self._prepared_key.d).decode("ASCII"), + "p": long_to_base64(self._prepared_key.p).decode("ASCII"), + "q": long_to_base64(self._prepared_key.q).decode("ASCII"), + "dp": long_to_base64(self._prepared_key.exp1).decode("ASCII"), + "dq": long_to_base64(self._prepared_key.exp2).decode("ASCII"), + "qi": long_to_base64(self._prepared_key.coef).decode("ASCII"), + } + ) return data + + def wrap_key(self, key_data): + if not self.is_public(): + warnings.warn("Attempting to encrypt a message with a private key." " This is not recommended.") + wrapped_key = pyrsa.encrypt(key_data, self._prepared_key) + return wrapped_key + + def unwrap_key(self, wrapped_key): + try: + unwrapped_key = pyrsa.decrypt(wrapped_key, self._prepared_key) + except DecryptionError as e: + raise JWEError(e) + return unwrapped_key diff --git a/jose/constants.py b/jose/constants.py index eb14654..ab4d74d 100644 --- a/jose/constants.py +++ b/jose/constants.py @@ -1,39 +1,98 @@ import hashlib -class Algorithms(object): - NONE = 'none' - HS256 = 'HS256' - HS384 = 'HS384' - HS512 = 'HS512' - RS256 = 'RS256' - RS384 = 'RS384' - RS512 = 'RS512' - ES256 = 'ES256' - ES384 = 'ES384' - ES512 = 'ES512' +class Algorithms: + # DS Algorithms + NONE = "none" + HS256 = "HS256" + HS384 = "HS384" + HS512 = "HS512" + RS256 = "RS256" + RS384 = "RS384" + RS512 = "RS512" + ES256 = "ES256" + ES384 = "ES384" + ES512 = "ES512" + + # Content Encryption Algorithms + A128CBC_HS256 = "A128CBC-HS256" + A192CBC_HS384 = "A192CBC-HS384" + A256CBC_HS512 = "A256CBC-HS512" + A128GCM = "A128GCM" + A192GCM = "A192GCM" + A256GCM = "A256GCM" + + # Pseudo algorithm for encryption + A128CBC = "A128CBC" + A192CBC = "A192CBC" + A256CBC = "A256CBC" + + # CEK Encryption Algorithms + DIR = "dir" + RSA1_5 = "RSA1_5" + RSA_OAEP = "RSA-OAEP" + RSA_OAEP_256 = "RSA-OAEP-256" + A128KW = "A128KW" + A192KW = "A192KW" + A256KW = "A256KW" + ECDH_ES = "ECDH-ES" + ECDH_ES_A128KW = "ECDH-ES+A128KW" + ECDH_ES_A192KW = "ECDH-ES+A192KW" + ECDH_ES_A256KW = "ECDH-ES+A256KW" + A128GCMKW = "A128GCMKW" + A192GCMKW = "A192GCMKW" + A256GCMKW = "A256GCMKW" + PBES2_HS256_A128KW = "PBES2-HS256+A128KW" + PBES2_HS384_A192KW = "PBES2-HS384+A192KW" + PBES2_HS512_A256KW = "PBES2-HS512+A256KW" + + # Compression Algorithms + DEF = "DEF" HMAC = {HS256, HS384, HS512} - RSA = {RS256, RS384, RS512} - EC = {ES256, ES384, ES512} + RSA_DS = {RS256, RS384, RS512} + RSA_KW = {RSA1_5, RSA_OAEP, RSA_OAEP_256} + RSA = RSA_DS.union(RSA_KW) + EC_DS = {ES256, ES384, ES512} + EC_KW = {ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW} + EC = EC_DS.union(EC_KW) + AES_PSEUDO = {A128CBC, A192CBC, A256CBC, A128GCM, A192GCM, A256GCM} + AES_JWE_ENC = {A128CBC_HS256, A192CBC_HS384, A256CBC_HS512, A128GCM, A192GCM, A256GCM} + AES_ENC = AES_JWE_ENC.union(AES_PSEUDO) + AES_KW = {A128KW, A192KW, A256KW} + AEC_GCM_KW = {A128GCMKW, A192GCMKW, A256GCMKW} + AES = AES_ENC.union(AES_KW) + PBES2_KW = {PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW} + + HMAC_AUTH_TAG = {A128CBC_HS256, A192CBC_HS384, A256CBC_HS512} + GCM = {A128GCM, A192GCM, A256GCM} - SUPPORTED = HMAC.union(RSA).union(EC) + SUPPORTED = HMAC.union(RSA_DS).union(EC_DS).union([DIR]).union(AES_JWE_ENC).union(RSA_KW).union(AES_KW) - ALL = SUPPORTED.union([NONE]) + ALL = SUPPORTED.union([NONE]).union(AEC_GCM_KW).union(EC_KW).union(PBES2_KW) HASHES = { HS256: hashlib.sha256, HS384: hashlib.sha384, HS512: hashlib.sha512, RS256: hashlib.sha256, RS384: hashlib.sha384, RS512: hashlib.sha512, ES256: hashlib.sha256, ES384: hashlib.sha384, ES512: hashlib.sha512, } KEYS = {} ALGORITHMS = Algorithms() + + +class Zips: + DEF = "DEF" + NONE = None + SUPPORTED = {DEF, NONE} + + +ZIPS = Zips() diff --git a/jose/exceptions.py b/jose/exceptions.py index 22334d6..e8edc3b 100644 --- a/jose/exceptions.py +++ b/jose/exceptions.py @@ -1,36 +1,59 @@ - - class JOSEError(Exception): pass class JWSError(JOSEError): pass class JWSSignatureError(JWSError): pass class JWSAlgorithmError(JWSError): pass class JWTError(JOSEError): pass class JWTClaimsError(JWTError): pass -class JWTSignatureError(JWTError): +class ExpiredSignatureError(JWTError): pass -class ExpiredSignatureError(JWTError): +class JWKError(JOSEError): pass -class JWKError(JOSEError): +class JWEError(JOSEError): + """Base error for all JWE errors""" + + pass + + +class JWEParseError(JWEError): + """Could not parse the JWE string provided""" + + pass + + +class JWEInvalidAuth(JWEError): + """ + The authentication tag did not match the protected sections of the + JWE string provided + """ + + pass + + +class JWEAlgorithmUnsupportedError(JWEError): + """ + The JWE algorithm is not supported by the backend + """ + pass diff --git a/jose/jwe.py b/jose/jwe.py new file mode 100644 index 0000000..2c387ff --- /dev/null +++ b/jose/jwe.py @@ -0,0 +1,607 @@ +import binascii +import json +import zlib +from collections.abc import Mapping +from struct import pack + +from . import jwk +from .backends import get_random_bytes +from .constants import ALGORITHMS, ZIPS +from .exceptions import JWEError, JWEParseError +from .utils import base64url_decode, base64url_encode, ensure_binary + + +def encrypt(plaintext, key, encryption=ALGORITHMS.A256GCM, algorithm=ALGORITHMS.DIR, zip=None, cty=None, kid=None): + """Encrypts plaintext and returns a JWE cmpact serialization string. + + Args: + plaintext (bytes): A bytes object to encrypt + key (str or dict): The key(s) to use for encrypting the content. Can be + individual JWK or JWK set. + encryption (str, optional): The content encryption algorithm used to + perform authenticated encryption on the plaintext to produce the + ciphertext and the Authentication Tag. Defaults to A256GCM. + algorithm (str, optional): The cryptographic algorithm used + to encrypt or determine the value of the CEK. Defaults to dir. + zip (str, optional): The compression algorithm) applied to the + plaintext before encryption. Defaults to None. + cty (str, optional): The media type for the secured content. + See http://www.iana.org/assignments/media-types/media-types.xhtml + kid (str, optional): Key ID for the provided key + + Returns: + bytes: The string representation of the header, encrypted key, + initialization vector, ciphertext, and authentication tag. + + Raises: + JWEError: If there is an error signing the token. + + Examples: + >>> from jose import jwe + >>> jwe.encrypt('Hello, World!', 'asecret128bitkey', algorithm='dir', encryption='A128GCM') + 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..McILMB3dYsNJSuhcDzQshA.OfX9H_mcUpHDeRM4IA.CcnTWqaqxNsjT4eCaUABSg' + + """ + plaintext = ensure_binary(plaintext) # Make sure it's bytes + if algorithm not in ALGORITHMS.SUPPORTED: + raise JWEError("Algorithm %s not supported." % algorithm) + if encryption not in ALGORITHMS.SUPPORTED: + raise JWEError("Algorithm %s not supported." % encryption) + key = jwk.construct(key, algorithm) + encoded_header = _encoded_header(algorithm, encryption, zip, cty, kid) + + plaintext = _compress(zip, plaintext) + enc_cek, iv, cipher_text, auth_tag = _encrypt_and_auth(key, algorithm, encryption, zip, plaintext, encoded_header) + + jwe_string = _jwe_compact_serialize(encoded_header, enc_cek, iv, cipher_text, auth_tag) + return jwe_string + + +def decrypt(jwe_str, key): + """Decrypts a JWE compact serialized string and returns the plaintext. + + Args: + jwe_str (str): A JWE to be decrypt. + key (str or dict): A key to attempt to decrypt the payload with. Can be + individual JWK or JWK set. + + Returns: + bytes: The plaintext bytes, assuming the authentication tag is valid. + + Raises: + JWEError: If there is an exception verifying the token. + + Examples: + >>> from jose import jwe + >>> jwe.decrypt(jwe_string, 'asecret128bitkey') + 'Hello, World!' + """ + header, encoded_header, encrypted_key, iv, cipher_text, auth_tag = _jwe_compact_deserialize(jwe_str) + + # Verify that the implementation understands and can process all + # fields that it is required to support, whether required by this + # specification, by the algorithms being used, or by the "crit" + # Header Parameter value, and that the values of those parameters + # are also understood and supported. + + try: + # Determine the Key Management Mode employed by the algorithm + # specified by the "alg" (algorithm) Header Parameter. + alg = header["alg"] + enc = header["enc"] + if alg not in ALGORITHMS.SUPPORTED: + raise JWEError("Algorithm %s not supported." % alg) + if enc not in ALGORITHMS.SUPPORTED: + raise JWEError("Algorithm %s not supported." % enc) + + except KeyError: + raise JWEParseError("alg and enc headers are required!") + + # Verify that the JWE uses a key known to the recipient. + key = jwk.construct(key, alg) + + # When Direct Key Agreement or Key Agreement with Key Wrapping are + # employed, use the key agreement algorithm to compute the value + # of the agreed upon key. When Direct Key Agreement is employed, + # let the CEK be the agreed upon key. When Key Agreement with Key + # Wrapping is employed, the agreed upon key will be used to + # decrypt the JWE Encrypted Key. + # + # When Key Wrapping, Key Encryption, or Key Agreement with Key + # Wrapping are employed, decrypt the JWE Encrypted Key to produce + # the CEK. The CEK MUST have a length equal to that required for + # the content encryption algorithm. Note that when there are + # multiple recipients, each recipient will only be able to decrypt + # JWE Encrypted Key values that were encrypted to a key in that + # recipient's possession. It is therefore normal to only be able + # to decrypt one of the per-recipient JWE Encrypted Key values to + # obtain the CEK value. Also, see Section 11.5 for security + # considerations on mitigating timing attacks. + if alg == ALGORITHMS.DIR: + # When Direct Key Agreement or Direct Encryption are employed, + # verify that the JWE Encrypted Key value is an empty octet + # sequence. + + # Record whether the CEK could be successfully determined for this + # recipient or not. + cek_valid = encrypted_key == b"" + + # When Direct Encryption is employed, let the CEK be the shared + # symmetric key. + cek_bytes = _get_key_bytes_from_key(key) + else: + try: + cek_bytes = key.unwrap_key(encrypted_key) + + # Record whether the CEK could be successfully determined for this + # recipient or not. + cek_valid = True + except NotImplementedError: + raise JWEError(f"alg {alg} is not implemented") + except Exception: + # Record whether the CEK could be successfully determined for this + # recipient or not. + cek_valid = False + + # To mitigate the attacks described in RFC 3218 [RFC3218], the + # recipient MUST NOT distinguish between format, padding, and length + # errors of encrypted keys. It is strongly recommended, in the event + # of receiving an improperly formatted key, that the recipient + # substitute a randomly generated CEK and proceed to the next step, to + # mitigate timing attacks. + cek_bytes = _get_random_cek_bytes_for_enc(enc) + + # Compute the Encoded Protected Header value BASE64URL(UTF8(JWE + # Protected Header)). If the JWE Protected Header is not present + # (which can only happen when using the JWE JSON Serialization and + # no "protected" member is present), let this value be the empty + # string. + protected_header = encoded_header + + # Let the Additional Authenticated Data encryption parameter be + # ASCII(Encoded Protected Header). However, if a JWE AAD value is + # present (which can only be the case when using the JWE JSON + # Serialization), instead let the Additional Authenticated Data + # encryption parameter be ASCII(Encoded Protected Header || '.' || + # BASE64URL(JWE AAD)). + aad = protected_header + + # Decrypt the JWE Ciphertext using the CEK, the JWE Initialization + # Vector, the Additional Authenticated Data value, and the JWE + # Authentication Tag (which is the Authentication Tag input to the + # calculation) using the specified content encryption algorithm, + # returning the decrypted plaintext and validating the JWE + # Authentication Tag in the manner specified for the algorithm, + # rejecting the input without emitting any decrypted output if the + # JWE Authentication Tag is incorrect. + try: + plain_text = _decrypt_and_auth(cek_bytes, enc, cipher_text, iv, aad, auth_tag) + except NotImplementedError: + raise JWEError(f"enc {enc} is not implemented") + except Exception as e: + raise JWEError(e) + + # If a "zip" parameter was included, uncompress the decrypted + # plaintext using the specified compression algorithm. + if plain_text is not None: + plain_text = _decompress(header.get("zip"), plain_text) + + return plain_text if cek_valid else None + + +def get_unverified_header(jwe_str): + """Returns the decoded headers without verification of any kind. + + Args: + jwe_str (str): A compact serialized JWE to decode the headers from. + + Returns: + dict: The dict representation of the JWE headers. + + Raises: + JWEError: If there is an exception decoding the JWE. + """ + header = _jwe_compact_deserialize(jwe_str)[0] + return header + + +def _decrypt_and_auth(cek_bytes, enc, cipher_text, iv, aad, auth_tag): + """ + Decrypt and verify the data + + Args: + cek_bytes (bytes): cek to derive encryption and possible auth key to + verify the auth tag + cipher_text (bytes): Encrypted data + iv (bytes): Initialization vector (iv) used to encrypt data + aad (bytes): Additional Authenticated Data used to verify the data + auth_tag (bytes): Authentication ntag to verify the data + + Returns: + (bytes): Decrypted data + """ + # Decrypt the JWE Ciphertext using the CEK, the JWE Initialization + # Vector, the Additional Authenticated Data value, and the JWE + # Authentication Tag (which is the Authentication Tag input to the + # calculation) using the specified content encryption algorithm, + # returning the decrypted plaintext + # and validating the JWE + # Authentication Tag in the manner specified for the algorithm, + if enc in ALGORITHMS.HMAC_AUTH_TAG: + encryption_key, mac_key, key_len = _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc) + auth_tag_check = _auth_tag(cipher_text, iv, aad, mac_key, key_len) + elif enc in ALGORITHMS.GCM: + encryption_key = jwk.construct(cek_bytes, enc) + auth_tag_check = auth_tag # GCM check auth on decrypt + else: + raise NotImplementedError(f"enc {enc} is not implemented!") + + plaintext = encryption_key.decrypt(cipher_text, iv, aad, auth_tag) + if auth_tag != auth_tag_check: + raise JWEError("Invalid JWE Auth Tag") + + return plaintext + + +def _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc): + derived_key_len = len(cek_bytes) // 2 + mac_key_bytes = cek_bytes[0:derived_key_len] + mac_key = _get_hmac_key(enc, mac_key_bytes) + encryption_key_bytes = cek_bytes[-derived_key_len:] + encryption_alg, _ = enc.split("-") + encryption_key = jwk.construct(encryption_key_bytes, encryption_alg) + return encryption_key, mac_key, derived_key_len + + +def _jwe_compact_deserialize(jwe_bytes): + """ + Deserialize and verify the header and segments are appropriate. + + Args: + jwe_bytes (bytes): The compact serialized JWE + Returns: + (dict, bytes, bytes, bytes, bytes, bytes) + """ + + # Base64url decode the encoded representations of the JWE + # Protected Header, the JWE Encrypted Key, the JWE Initialization + # Vector, the JWE Ciphertext, the JWE Authentication Tag, and the + # JWE AAD, following the restriction that no line breaks, + # whitespace, or other additional characters have been used. + jwe_bytes = ensure_binary(jwe_bytes) + try: + header_segment, encrypted_key_segment, iv_segment, cipher_text_segment, auth_tag_segment = jwe_bytes.split( + b".", 4 + ) + header_data = base64url_decode(header_segment) + except ValueError: + raise JWEParseError("Not enough segments") + except (TypeError, binascii.Error): + raise JWEParseError("Invalid header") + + # Verify that the octet sequence resulting from decoding the + # encoded JWE Protected Header is a UTF-8-encoded representation + # of a completely valid JSON object conforming to RFC 7159 + # [RFC7159]; let the JWE Protected Header be this JSON object. + # + # If using the JWE Compact Serialization, let the JOSE Header be + # the JWE Protected Header. Otherwise, when using the JWE JSON + # Serialization, let the JOSE Header be the union of the members + # of the JWE Protected Header, the JWE Shared Unprotected Header + # and the corresponding JWE Per-Recipient Unprotected Header, all + # of which must be completely valid JSON objects. During this + # step, verify that the resulting JOSE Header does not contain + # duplicate Header Parameter names. When using the JWE JSON + # Serialization, this restriction includes that the same Header + # Parameter name also MUST NOT occur in distinct JSON object + # values that together comprise the JOSE Header. + + try: + header = json.loads(header_data) + except ValueError as e: + raise JWEParseError(f"Invalid header string: {e}") + + if not isinstance(header, Mapping): + raise JWEParseError("Invalid header string: must be a json object") + + try: + encrypted_key = base64url_decode(encrypted_key_segment) + except (TypeError, binascii.Error): + raise JWEParseError("Invalid encrypted key") + + try: + iv = base64url_decode(iv_segment) + except (TypeError, binascii.Error): + raise JWEParseError("Invalid IV") + + try: + ciphertext = base64url_decode(cipher_text_segment) + except (TypeError, binascii.Error): + raise JWEParseError("Invalid cyphertext") + + try: + auth_tag = base64url_decode(auth_tag_segment) + except (TypeError, binascii.Error): + raise JWEParseError("Invalid auth tag") + + return header, header_segment, encrypted_key, iv, ciphertext, auth_tag + + +def _encoded_header(alg, enc, zip, cty, kid): + """ + Generate an appropriate JOSE header based on the values provided + Args: + alg (str): Key wrap/negotiation algorithm + enc (str): Encryption algorithm + zip (str): Compression method + cty (str): Content type of the encrypted data + kid (str): ID for the key used for the operation + + Returns: + bytes: JSON object of header based on input + """ + header = {"alg": alg, "enc": enc} + if zip: + header["zip"] = zip + if cty: + header["cty"] = cty + if kid: + header["kid"] = kid + json_header = json.dumps( + header, + separators=(",", ":"), + sort_keys=True, + ).encode("utf-8") + return base64url_encode(json_header) + + +def _big_endian(int_val): + return pack("!Q", int_val) + + +def _encrypt_and_auth(key, alg, enc, zip, plaintext, aad): + """ + Generate a content encryption key (cek) and initialization + vector (iv) based on enc and alg, compress the plaintext based on zip, + encrypt the compressed plaintext using the cek and iv based on enc + + Args: + key (Key): The key provided for encryption + alg (str): The algorithm use for key wrap/negotiation + enc (str): The encryption algorithm with which to encrypt the plaintext + zip (str): The compression algorithm with which to compress the plaintext + plaintext (bytes): The data to encrypt + aad (str): Additional authentication data utilized for generating an + auth tag + + Returns: + (bytes, bytes, bytes, bytes): A tuple of the following data + (key wrapped cek, iv, cipher text, auth tag) + """ + try: + cek_bytes, kw_cek = _get_cek(enc, alg, key) + except NotImplementedError: + raise JWEError(f"alg {alg} is not implemented") + + if enc in ALGORITHMS.HMAC_AUTH_TAG: + encryption_key, mac_key, key_len = _get_encryption_key_mac_key_and_key_length_from_cek(cek_bytes, enc) + iv, ciphertext, tag = encryption_key.encrypt(plaintext, aad) + auth_tag = _auth_tag(ciphertext, iv, aad, mac_key, key_len) + elif enc in ALGORITHMS.GCM: + encryption_key = jwk.construct(cek_bytes, enc) + iv, ciphertext, auth_tag = encryption_key.encrypt(plaintext, aad) + else: + raise NotImplementedError(f"enc {enc} is not implemented!") + + return kw_cek, iv, ciphertext, auth_tag + + +def _get_hmac_key(enc, mac_key_bytes): + """ + Get an HMACKey for the provided encryption algorithm and key bytes + + Args: + enc (str): Encryption algorithm + mac_key_bytes (bytes): vytes for the HMAC key + + Returns: + (HMACKey): The key to perform HMAC actions + """ + _, hash_alg = enc.split("-") + mac_key = jwk.construct(mac_key_bytes, hash_alg) + return mac_key + + +def _compress(zip, plaintext): + """ + Compress the plaintext based on the algorithm supplied + + Args: + zip (str): Compression Algorithm + plaintext (bytes): plaintext to compress + + Returns: + (bytes): Compressed plaintext + """ + if zip not in ZIPS.SUPPORTED: + raise NotImplementedError("ZIP {} is not supported!") + if zip is None: + compressed = plaintext + elif zip == ZIPS.DEF: + compressed = zlib.compress(plaintext) + else: + raise NotImplementedError("ZIP {} is not implemented!") + return compressed + + +def _decompress(zip, compressed): + """ + Decompress the plaintext based on the algorithm supplied + + Args: + zip (str): Compression Algorithm + plaintext (bytes): plaintext to decompress + + Returns: + (bytes): Compressed plaintext + """ + if zip not in ZIPS.SUPPORTED: + raise NotImplementedError("ZIP {} is not supported!") + if zip is None: + decompressed = compressed + elif zip == ZIPS.DEF: + decompressed = zlib.decompress(compressed) + else: + raise NotImplementedError("ZIP {} is not implemented!") + return decompressed + + +def _get_cek(enc, alg, key): + """ + Get the content encryption key + + Args: + enc (str): Encryption algorithm + alg (str): kwy wrap/negotiation algorithm + key (Key): Key provided to encryption method + + Return: + (bytes, bytes): Tuple of (cek bytes and wrapped cek) + """ + if alg == ALGORITHMS.DIR: + cek, wrapped_cek = _get_direct_key_wrap_cek(key) + else: + cek, wrapped_cek = _get_key_wrap_cek(enc, key) + + return cek, wrapped_cek + + +def _get_direct_key_wrap_cek(key): + """ + Get the cek and wrapped cek from the encryption key direct + + Args: + key (Key): Key provided to encryption method + + Return: + (Key, bytes): Tuple of (cek Key object and wrapped cek) + """ + # Get the JWK data to determine how to derive the cek + jwk_data = key.to_dict() + if jwk_data["kty"] == "oct": + # Get the last half of an octal key as the cek + cek_bytes = _get_key_bytes_from_key(key) + wrapped_cek = b"" + else: + raise NotImplementedError("JWK type {} not supported!".format(jwk_data["kty"])) + return cek_bytes, wrapped_cek + + +def _get_key_bytes_from_key(key): + """ + Get the raw key bytes from a Key object + + Args: + key (Key): Key from which to extract the raw key bytes + Returns: + (bytes) key data + """ + jwk_data = key.to_dict() + encoded_key = jwk_data["k"] + cek_bytes = base64url_decode(encoded_key) + return cek_bytes + + +def _get_key_wrap_cek(enc, key): + """_get_rsa_key_wrap_cek + Get the content encryption key for RSA key wrap + + Args: + enc (str): Encryption algorithm + key (Key): Key provided to encryption method + + Returns: + (Key, bytes): Tuple of (cek Key object and wrapped cek) + """ + cek_bytes = _get_random_cek_bytes_for_enc(enc) + wrapped_cek = key.wrap_key(cek_bytes) + return cek_bytes, wrapped_cek + + +def _get_random_cek_bytes_for_enc(enc): + """ + Get the random cek bytes based on the encryptionn algorithm + + Args: + enc (str): Encryption algorithm + + Returns: + (bytes) random bytes for cek key + """ + if enc == ALGORITHMS.A128GCM: + num_bits = 128 + elif enc == ALGORITHMS.A192GCM: + num_bits = 192 + elif enc in (ALGORITHMS.A128CBC_HS256, ALGORITHMS.A256GCM): + num_bits = 256 + elif enc == ALGORITHMS.A192CBC_HS384: + num_bits = 384 + elif enc == ALGORITHMS.A256CBC_HS512: + num_bits = 512 + else: + raise NotImplementedError(f"{enc} not supported") + cek_bytes = get_random_bytes(num_bits // 8) + return cek_bytes + + +def _auth_tag(ciphertext, iv, aad, mac_key, tag_length): + """ + Get ann auth tag from the provided data + + Args: + ciphertext (bytes): Encrypted value + iv (bytes): Initialization vector + aad (bytes): Additional Authenticated Data + mac_key (bytes): Key to use in generating the MAC + tag_length (int): How log the tag should be + + Returns: + (bytes) Auth tag + """ + al = _big_endian(len(aad) * 8) + auth_tag_input = aad + iv + ciphertext + al + signature = mac_key.sign(auth_tag_input) + auth_tag = signature[0:tag_length] + return auth_tag + + +def _jwe_compact_serialize(encoded_header, encrypted_cek, iv, cipher_text, auth_tag): + """ + Generate a compact serialized JWE + + Args: + encoded_header (bytes): Base64 URL Encoded JWE header JSON + encrypted_cek (bytes): Encrypted content encryption key (cek) + iv (bytes): Initialization vector (IV) + cipher_text (bytes): Cipher text + auth_tag (bytes): JWE Auth Tag + + Returns: + (str): JWE compact serialized string + """ + cipher_text = ensure_binary(cipher_text) + encoded_encrypted_cek = base64url_encode(encrypted_cek) + encoded_iv = base64url_encode(iv) + encoded_cipher_text = base64url_encode(cipher_text) + encoded_auth_tag = base64url_encode(auth_tag) + return ( + encoded_header + + b"." + + encoded_encrypted_cek + + b"." + + encoded_iv + + b"." + + encoded_cipher_text + + b"." + + encoded_auth_tag + ) diff --git a/jose/jwk.py b/jose/jwk.py index 87f30b4..7afc054 100644 --- a/jose/jwk.py +++ b/jose/jwk.py @@ -1,142 +1,79 @@ - -import hashlib -import hmac -import six - +from jose.backends.base import Key from jose.constants import ALGORITHMS from jose.exceptions import JWKError -from jose.utils import base64url_decode, base64url_encode -from jose.utils import constant_time_string_compare -from jose.backends.base import Key try: from jose.backends import RSAKey # noqa: F401 except ImportError: pass try: from jose.backends import ECKey # noqa: F401 except ImportError: pass +try: + from jose.backends import AESKey # noqa: F401 +except ImportError: + pass + +try: + from jose.backends import DIRKey # noqa: F401 +except ImportError: + pass + +try: + from jose.backends import HMACKey # noqa: F401 +except ImportError: + pass + def get_key(algorithm): if algorithm in ALGORITHMS.KEYS: return ALGORITHMS.KEYS[algorithm] - elif algorithm in ALGORITHMS.HMAC: + elif algorithm in ALGORITHMS.HMAC: # noqa: F811 return HMACKey elif algorithm in ALGORITHMS.RSA: from jose.backends import RSAKey # noqa: F811 + return RSAKey elif algorithm in ALGORITHMS.EC: from jose.backends import ECKey # noqa: F811 + return ECKey + elif algorithm in ALGORITHMS.AES: + from jose.backends import AESKey # noqa: F811 + + return AESKey + elif algorithm == ALGORITHMS.DIR: + from jose.backends import DIRKey # noqa: F811 + + return DIRKey return None def register_key(algorithm, key_class): if not issubclass(key_class, Key): - raise TypeError("Key class not a subclass of jwk.Key") + raise TypeError("Key class is not a subclass of jwk.Key") ALGORITHMS.KEYS[algorithm] = key_class ALGORITHMS.SUPPORTED.add(algorithm) return True def construct(key_data, algorithm=None): """ Construct a Key object for the given algorithm with the given key_data. """ # Allow for pulling the algorithm off of the passed in jwk. if not algorithm and isinstance(key_data, dict): - algorithm = key_data.get('alg', None) + algorithm = key_data.get("alg", None) if not algorithm: - raise JWKError('Unable to find a algorithm for key: %s' % key_data) + raise JWKError("Unable to find an algorithm for key: %s" % key_data) key_class = get_key(algorithm) if not key_class: - raise JWKError('Unable to find a algorithm for key: %s' % key_data) + raise JWKError("Unable to find an algorithm for key: %s" % key_data) return key_class(key_data, algorithm) - - -def get_algorithm_object(algorithm): - algorithms = { - ALGORITHMS.HS256: 'SHA256', - ALGORITHMS.HS384: 'SHA384', - ALGORITHMS.HS512: 'SHA512', - ALGORITHMS.RS256: 'SHA256', - ALGORITHMS.RS384: 'SHA384', - ALGORITHMS.RS512: 'SHA512', - ALGORITHMS.ES256: 'SHA256', - ALGORITHMS.ES384: 'SHA384', - ALGORITHMS.ES512: 'SHA512', - } - key = get_key(algorithm) - attr = algorithms.get(algorithm, None) - return getattr(key, attr) - - -class HMACKey(Key): - """ - Performs signing and verification operations using HMAC - and the specified hash function. - """ - SHA256 = hashlib.sha256 - SHA384 = hashlib.sha384 - SHA512 = hashlib.sha512 - - def __init__(self, key, algorithm): - if algorithm not in ALGORITHMS.HMAC: - raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm) - self._algorithm = algorithm - self.hash_alg = get_algorithm_object(algorithm) - - if isinstance(key, dict): - self.prepared_key = self._process_jwk(key) - return - - if not isinstance(key, six.string_types) and not isinstance(key, bytes): - raise JWKError('Expecting a string- or bytes-formatted key.') - - if isinstance(key, six.text_type): - key = key.encode('utf-8') - - invalid_strings = [ - b'-----BEGIN PUBLIC KEY-----', - b'-----BEGIN RSA PUBLIC KEY-----', - b'-----BEGIN CERTIFICATE-----', - b'ssh-rsa' - ] - - if any(string_value in key for string_value in invalid_strings): - raise JWKError( - 'The specified key is an asymmetric key or x509 certificate and' - ' should not be used as an HMAC secret.') - - self.prepared_key = key - - def _process_jwk(self, jwk_dict): - if not jwk_dict.get('kty') == 'oct': - raise JWKError("Incorrect key type. Expected: 'oct', Recieved: %s" % jwk_dict.get('kty')) - - k = jwk_dict.get('k') - k = k.encode('utf-8') - k = bytes(k) - k = base64url_decode(k) - - return k - - def sign(self, msg): - return hmac.new(self.prepared_key, msg, self.hash_alg).digest() - - def verify(self, msg, sig): - return constant_time_string_compare(sig, self.sign(msg)) - - def to_dict(self): - return { - 'alg': self._algorithm, - 'kty': 'oct', - 'k': base64url_encode(self.prepared_key), - } diff --git a/jose/jws.py b/jose/jws.py index 293b32a..bfaf6bd 100644 --- a/jose/jws.py +++ b/jose/jws.py @@ -1,273 +1,266 @@ - import binascii import json -import six - -try: - from collections.abc import Mapping, Iterable # Python 3 -except ImportError: - from collections import Mapping, Iterable # Python 2, will be deprecated in Python 3.8 +from collections.abc import Iterable, Mapping from jose import jwk +from jose.backends.base import Key from jose.constants import ALGORITHMS -from jose.exceptions import JWSError -from jose.exceptions import JWSSignatureError -from jose.utils import base64url_encode -from jose.utils import base64url_decode +from jose.exceptions import JWSError, JWSSignatureError +from jose.utils import base64url_decode, base64url_encode def sign(payload, key, headers=None, algorithm=ALGORITHMS.HS256): """Signs a claims set and returns a JWS string. Args: payload (str or dict): A string to sign key (str or dict): The key to use for signing the claim set. Can be individual JWK or JWK set. headers (dict, optional): A set of headers that will be added to the default headers. Any headers that are added as additional headers will override the default headers. algorithm (str, optional): The algorithm to use for signing the the claims. Defaults to HS256. Returns: str: The string representation of the header, claims, and signature. Raises: JWSError: If there is an error signing the token. Examples: >>> jws.sign({'a': 'b'}, 'secret', algorithm='HS256') 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' """ if algorithm not in ALGORITHMS.SUPPORTED: - raise JWSError('Algorithm %s not supported.' % algorithm) + raise JWSError("Algorithm %s not supported." % algorithm) encoded_header = _encode_header(algorithm, additional_headers=headers) encoded_payload = _encode_payload(payload) signed_output = _sign_header_and_claims(encoded_header, encoded_payload, algorithm, key) return signed_output def verify(token, key, algorithms, verify=True): """Verifies a JWS string's signature. Args: token (str): A signed JWS to be verified. key (str or dict): A key to attempt to verify the payload with. Can be individual JWK or JWK set. algorithms (str or list): Valid algorithms that should be used to verify the JWS. Returns: str: The str representation of the payload, assuming the signature is valid. Raises: JWSError: If there is an exception verifying a token. Examples: >>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' >>> jws.verify(token, 'secret', algorithms='HS256') """ header, payload, signing_input, signature = _load(token) if verify: _verify_signature(signing_input, header, signature, key, algorithms) return payload def get_unverified_header(token): """Returns the decoded headers without verification of any kind. Args: token (str): A signed JWS to decode the headers from. Returns: dict: The dict representation of the token headers. Raises: JWSError: If there is an exception decoding the token. """ header, claims, signing_input, signature = _load(token) return header def get_unverified_headers(token): """Returns the decoded headers without verification of any kind. This is simply a wrapper of get_unverified_header() for backwards compatibility. Args: token (str): A signed JWS to decode the headers from. Returns: dict: The dict representation of the token headers. Raises: JWSError: If there is an exception decoding the token. """ return get_unverified_header(token) def get_unverified_claims(token): """Returns the decoded claims without verification of any kind. Args: token (str): A signed JWS to decode the headers from. Returns: str: The str representation of the token claims. Raises: JWSError: If there is an exception decoding the token. """ header, claims, signing_input, signature = _load(token) return claims def _encode_header(algorithm, additional_headers=None): - header = { - "typ": "JWT", - "alg": algorithm - } + header = {"typ": "JWT", "alg": algorithm} if additional_headers: header.update(additional_headers) json_header = json.dumps( header, - separators=(',', ':'), + separators=(",", ":"), sort_keys=True, - ).encode('utf-8') + ).encode("utf-8") return base64url_encode(json_header) def _encode_payload(payload): if isinstance(payload, Mapping): try: payload = json.dumps( payload, - separators=(',', ':'), - ).encode('utf-8') + separators=(",", ":"), + ).encode("utf-8") except ValueError: pass return base64url_encode(payload) -def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key_data): - signing_input = b'.'.join([encoded_header, encoded_claims]) +def _sign_header_and_claims(encoded_header, encoded_claims, algorithm, key): + signing_input = b".".join([encoded_header, encoded_claims]) try: - key = jwk.construct(key_data, algorithm) + if not isinstance(key, Key): + key = jwk.construct(key, algorithm) signature = key.sign(signing_input) except Exception as e: raise JWSError(e) encoded_signature = base64url_encode(signature) - encoded_string = b'.'.join([encoded_header, encoded_claims, encoded_signature]) + encoded_string = b".".join([encoded_header, encoded_claims, encoded_signature]) - return encoded_string.decode('utf-8') + return encoded_string.decode("utf-8") def _load(jwt): - if isinstance(jwt, six.text_type): - jwt = jwt.encode('utf-8') + if isinstance(jwt, str): + jwt = jwt.encode("utf-8") try: - signing_input, crypto_segment = jwt.rsplit(b'.', 1) - header_segment, claims_segment = signing_input.split(b'.', 1) + signing_input, crypto_segment = jwt.rsplit(b".", 1) + header_segment, claims_segment = signing_input.split(b".", 1) header_data = base64url_decode(header_segment) except ValueError: - raise JWSError('Not enough segments') + raise JWSError("Not enough segments") except (TypeError, binascii.Error): - raise JWSError('Invalid header padding') + raise JWSError("Invalid header padding") try: - header = json.loads(header_data.decode('utf-8')) + header = json.loads(header_data.decode("utf-8")) except ValueError as e: - raise JWSError('Invalid header string: %s' % e) + raise JWSError("Invalid header string: %s" % e) if not isinstance(header, Mapping): - raise JWSError('Invalid header string: must be a json object') + raise JWSError("Invalid header string: must be a json object") try: payload = base64url_decode(claims_segment) except (TypeError, binascii.Error): - raise JWSError('Invalid payload padding') + raise JWSError("Invalid payload padding") try: signature = base64url_decode(crypto_segment) except (TypeError, binascii.Error): - raise JWSError('Invalid crypto padding') + raise JWSError("Invalid crypto padding") return (header, payload, signing_input, signature) def _sig_matches_keys(keys, signing_input, signature, alg): for key in keys: - key = jwk.construct(key, alg) + if not isinstance(key, Key): + key = jwk.construct(key, alg) try: if key.verify(signing_input, signature): return True except Exception: pass return False def _get_keys(key): + if isinstance(key, Key): + return (key,) + try: - key = json.loads(key) + key = json.loads(key, parse_int=str, parse_float=str) except Exception: pass - # JWK Set per RFC 7517 - if 'keys' in key: - return key['keys'] - - # Individual JWK per RFC 7517 - elif 'kty' in key: - return (key,) - - # Some other mapping. Firebase uses just dict of kid, cert pairs - elif isinstance(key, Mapping): - values = key.values() - if values: - return values - return (key,) + if isinstance(key, Mapping): + if "keys" in key: + # JWK Set per RFC 7517 + return key["keys"] + elif "kty" in key: + # Individual JWK per RFC 7517 + return (key,) + else: + # Some other mapping. Firebase uses just dict of kid, cert pairs + values = key.values() + if values: + return values + return (key,) # Iterable but not text or mapping => list- or tuple-like - elif (isinstance(key, Iterable) and - not (isinstance(key, six.string_types) or isinstance(key, Mapping))): + elif isinstance(key, Iterable) and not (isinstance(key, str) or isinstance(key, bytes)): return key # Scalar value, wrap in tuple. else: return (key,) -def _verify_signature(signing_input, header, signature, key='', algorithms=None): +def _verify_signature(signing_input, header, signature, key="", algorithms=None): - alg = header.get('alg') + alg = header.get("alg") if not alg: - raise JWSError('No algorithm was specified in the JWS header.') + raise JWSError("No algorithm was specified in the JWS header.") if algorithms is not None and alg not in algorithms: - raise JWSError('The specified alg value is not allowed') + raise JWSError("The specified alg value is not allowed") keys = _get_keys(key) try: if not _sig_matches_keys(keys, signing_input, signature, alg): raise JWSSignatureError() except JWSSignatureError: - raise JWSError('Signature verification failed.') + raise JWSError("Signature verification failed.") except JWSError: - raise JWSError('Invalid or unsupported algorithm: %s' % alg) + raise JWSError("Invalid or unsupported algorithm: %s" % alg) diff --git a/jose/jwt.py b/jose/jwt.py index ee3b98d..3f2142e 100644 --- a/jose/jwt.py +++ b/jose/jwt.py @@ -1,507 +1,496 @@ - import json - from calendar import timegm -try: - from collections.abc import Mapping # Python3 -except ImportError: - from collections import Mapping # Python2, will be deprecated in Python 3.8 -from datetime import datetime -from datetime import timedelta -from six import string_types +from collections.abc import Mapping +from datetime import datetime, timedelta from jose import jws -from .exceptions import JWSError -from .exceptions import JWTClaimsError -from .exceptions import JWTError -from .exceptions import ExpiredSignatureError from .constants import ALGORITHMS -from .utils import timedelta_total_seconds, calculate_at_hash +from .exceptions import ExpiredSignatureError, JWSError, JWTClaimsError, JWTError +from .utils import calculate_at_hash, timedelta_total_seconds def encode(claims, key, algorithm=ALGORITHMS.HS256, headers=None, access_token=None): """Encodes a claims set and returns a JWT string. JWTs are JWS signed objects with a few reserved claims. Args: claims (dict): A claims set to sign key (str or dict): The key to use for signing the claim set. Can be individual JWK or JWK set. algorithm (str, optional): The algorithm to use for signing the the claims. Defaults to HS256. headers (dict, optional): A set of headers that will be added to the default headers. Any headers that are added as additional headers will override the default headers. access_token (str, optional): If present, the 'at_hash' claim will be calculated and added to the claims present in the 'claims' parameter. Returns: str: The string representation of the header, claims, and signature. Raises: JWTError: If there is an error encoding the claims. Examples: >>> jwt.encode({'a': 'b'}, 'secret', algorithm='HS256') 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' """ - for time_claim in ['exp', 'iat', 'nbf']: + for time_claim in ["exp", "iat", "nbf"]: # Convert datetime to a intDate value in known time-format claims if isinstance(claims.get(time_claim), datetime): claims[time_claim] = timegm(claims[time_claim].utctimetuple()) if access_token: - claims['at_hash'] = calculate_at_hash(access_token, - ALGORITHMS.HASHES[algorithm]) + claims["at_hash"] = calculate_at_hash(access_token, ALGORITHMS.HASHES[algorithm]) return jws.sign(claims, key, headers=headers, algorithm=algorithm) -def decode(token, key, algorithms=None, options=None, audience=None, - issuer=None, subject=None, access_token=None): +def decode(token, key, algorithms=None, options=None, audience=None, issuer=None, subject=None, access_token=None): """Verifies a JWT string's signature and validates reserved claims. Args: token (str): A signed JWS to be verified. key (str or dict): A key to attempt to verify the payload with. Can be individual JWK or JWK set. algorithms (str or list): Valid algorithms that should be used to verify the JWS. audience (str): The intended audience of the token. If the "aud" claim is included in the claim set, then the audience must be included and must equal the provided claim. issuer (str or iterable): Acceptable value(s) for the issuer of the token. If the "iss" claim is included in the claim set, then the issuer must be given and the claim in the token must be among the acceptable values. subject (str): The subject of the token. If the "sub" claim is included in the claim set, then the subject must be included and must equal the provided claim. access_token (str): An access token string. If the "at_hash" claim is included in the claim set, then the access_token must be included, and it must match the "at_hash" claim. options (dict): A dictionary of options for skipping validation steps. defaults = { 'verify_signature': True, 'verify_aud': True, 'verify_iat': True, 'verify_exp': True, 'verify_nbf': True, 'verify_iss': True, 'verify_sub': True, 'verify_jti': True, 'verify_at_hash': True, 'require_aud': False, 'require_iat': False, 'require_exp': False, 'require_nbf': False, 'require_iss': False, 'require_sub': False, 'require_jti': False, 'require_at_hash': False, 'leeway': 0, } Returns: dict: The dict representation of the claims set, assuming the signature is valid and all requested data validation passes. Raises: JWTError: If the signature is invalid in any way. ExpiredSignatureError: If the signature has expired. JWTClaimsError: If any claim is invalid in any way. Examples: >>> payload = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' >>> jwt.decode(payload, 'secret', algorithms='HS256') """ defaults = { - 'verify_signature': True, - 'verify_aud': True, - 'verify_iat': True, - 'verify_exp': True, - 'verify_nbf': True, - 'verify_iss': True, - 'verify_sub': True, - 'verify_jti': True, - 'verify_at_hash': True, - 'require_aud': False, - 'require_iat': False, - 'require_exp': False, - 'require_nbf': False, - 'require_iss': False, - 'require_sub': False, - 'require_jti': False, - 'require_at_hash': False, - 'leeway': 0, + "verify_signature": True, + "verify_aud": True, + "verify_iat": True, + "verify_exp": True, + "verify_nbf": True, + "verify_iss": True, + "verify_sub": True, + "verify_jti": True, + "verify_at_hash": True, + "require_aud": False, + "require_iat": False, + "require_exp": False, + "require_nbf": False, + "require_iss": False, + "require_sub": False, + "require_jti": False, + "require_at_hash": False, + "leeway": 0, } if options: defaults.update(options) - verify_signature = defaults.get('verify_signature', True) + verify_signature = defaults.get("verify_signature", True) try: payload = jws.verify(token, key, algorithms, verify=verify_signature) except JWSError as e: raise JWTError(e) # Needed for at_hash verification - algorithm = jws.get_unverified_header(token)['alg'] + algorithm = jws.get_unverified_header(token)["alg"] try: - claims = json.loads(payload.decode('utf-8')) + claims = json.loads(payload.decode("utf-8")) except ValueError as e: - raise JWTError('Invalid payload string: %s' % e) + raise JWTError("Invalid payload string: %s" % e) if not isinstance(claims, Mapping): - raise JWTError('Invalid payload string: must be a json object') - - _validate_claims(claims, audience=audience, issuer=issuer, - subject=subject, algorithm=algorithm, - access_token=access_token, - options=defaults) + raise JWTError("Invalid payload string: must be a json object") + + _validate_claims( + claims, + audience=audience, + issuer=issuer, + subject=subject, + algorithm=algorithm, + access_token=access_token, + options=defaults, + ) return claims def get_unverified_header(token): """Returns the decoded headers without verification of any kind. Args: token (str): A signed JWT to decode the headers from. Returns: dict: The dict representation of the token headers. Raises: JWTError: If there is an exception decoding the token. """ try: headers = jws.get_unverified_headers(token) except Exception: - raise JWTError('Error decoding token headers.') + raise JWTError("Error decoding token headers.") return headers def get_unverified_headers(token): """Returns the decoded headers without verification of any kind. This is simply a wrapper of get_unverified_header() for backwards compatibility. Args: token (str): A signed JWT to decode the headers from. Returns: dict: The dict representation of the token headers. Raises: JWTError: If there is an exception decoding the token. """ return get_unverified_header(token) def get_unverified_claims(token): """Returns the decoded claims without verification of any kind. Args: token (str): A signed JWT to decode the headers from. Returns: dict: The dict representation of the token claims. Raises: JWTError: If there is an exception decoding the token. """ try: claims = jws.get_unverified_claims(token) except Exception: - raise JWTError('Error decoding token claims.') + raise JWTError("Error decoding token claims.") try: - claims = json.loads(claims.decode('utf-8')) + claims = json.loads(claims.decode("utf-8")) except ValueError as e: - raise JWTError('Invalid claims string: %s' % e) + raise JWTError("Invalid claims string: %s" % e) if not isinstance(claims, Mapping): - raise JWTError('Invalid claims string: must be a json object') + raise JWTError("Invalid claims string: must be a json object") return claims def _validate_iat(claims): """Validates that the 'iat' claim is valid. The "iat" (issued at) claim identifies the time at which the JWT was issued. This claim can be used to determine the age of the JWT. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. """ - if 'iat' not in claims: + if "iat" not in claims: return try: - int(claims['iat']) + int(claims["iat"]) except ValueError: - raise JWTClaimsError('Issued At claim (iat) must be an integer.') + raise JWTClaimsError("Issued At claim (iat) must be an integer.") def _validate_nbf(claims, leeway=0): """Validates that the 'nbf' claim is valid. The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing. The processing of the "nbf" claim requires that the current date/time MUST be after or equal to the not-before date/time listed in the "nbf" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. leeway (int): The number of seconds of skew that is allowed. """ - if 'nbf' not in claims: + if "nbf" not in claims: return try: - nbf = int(claims['nbf']) + nbf = int(claims["nbf"]) except ValueError: - raise JWTClaimsError('Not Before claim (nbf) must be an integer.') + raise JWTClaimsError("Not Before claim (nbf) must be an integer.") now = timegm(datetime.utcnow().utctimetuple()) if nbf > (now + leeway): - raise JWTClaimsError('The token is not yet valid (nbf)') + raise JWTClaimsError("The token is not yet valid (nbf)") def _validate_exp(claims, leeway=0): """Validates that the 'exp' claim is valid. The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing. The processing of the "exp" claim requires that the current date/time MUST be before the expiration date/time listed in the "exp" claim. Implementers MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew. Its value MUST be a number containing a NumericDate value. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. leeway (int): The number of seconds of skew that is allowed. """ - if 'exp' not in claims: + if "exp" not in claims: return try: - exp = int(claims['exp']) + exp = int(claims["exp"]) except ValueError: - raise JWTClaimsError('Expiration Time claim (exp) must be an integer.') + raise JWTClaimsError("Expiration Time claim (exp) must be an integer.") now = timegm(datetime.utcnow().utctimetuple()) if exp < (now - leeway): - raise ExpiredSignatureError('Signature has expired.') + raise ExpiredSignatureError("Signature has expired.") def _validate_aud(claims, audience=None): """Validates that the 'aud' claim is valid. The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. audience (str): The audience that is verifying the token. """ - if 'aud' not in claims: + if "aud" not in claims: # if audience: # raise JWTError('Audience claim expected, but not in claims') return - audience_claims = claims['aud'] - if isinstance(audience_claims, string_types): + audience_claims = claims["aud"] + if isinstance(audience_claims, str): audience_claims = [audience_claims] if not isinstance(audience_claims, list): - raise JWTClaimsError('Invalid claim format in token') - if any(not isinstance(c, string_types) for c in audience_claims): - raise JWTClaimsError('Invalid claim format in token') + raise JWTClaimsError("Invalid claim format in token") + if any(not isinstance(c, str) for c in audience_claims): + raise JWTClaimsError("Invalid claim format in token") if audience not in audience_claims: - raise JWTClaimsError('Invalid audience') + raise JWTClaimsError("Invalid audience") def _validate_iss(claims, issuer=None): """Validates that the 'iss' claim is valid. The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. issuer (str or iterable): Acceptable value(s) for the issuer that signed the token. """ if issuer is not None: - if isinstance(issuer, string_types): + if isinstance(issuer, str): issuer = (issuer,) - if claims.get('iss') not in issuer: - raise JWTClaimsError('Invalid issuer') + if claims.get("iss") not in issuer: + raise JWTClaimsError("Invalid issuer") def _validate_sub(claims, subject=None): """Validates that the 'sub' claim is valid. The "sub" (subject) claim identifies the principal that is the subject of the JWT. The claims in a JWT are normally statements about the subject. The subject value MUST either be scoped to be locally unique in the context of the issuer or be globally unique. The processing of this claim is generally application specific. The "sub" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. subject (str): The subject of the token. """ - if 'sub' not in claims: + if "sub" not in claims: return - if not isinstance(claims['sub'], string_types): - raise JWTClaimsError('Subject must be a string.') + if not isinstance(claims["sub"], str): + raise JWTClaimsError("Subject must be a string.") if subject is not None: - if claims.get('sub') != subject: - raise JWTClaimsError('Invalid subject') + if claims.get("sub") != subject: + raise JWTClaimsError("Invalid subject") def _validate_jti(claims): """Validates that the 'jti' claim is valid. The "jti" (JWT ID) claim provides a unique identifier for the JWT. The identifier value MUST be assigned in a manner that ensures that there is a negligible probability that the same value will be accidentally assigned to a different data object; if the application uses multiple issuers, collisions MUST be prevented among values produced by different issuers as well. The "jti" claim can be used to prevent the JWT from being replayed. The "jti" value is a case- sensitive string. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. """ - if 'jti' not in claims: + if "jti" not in claims: return - if not isinstance(claims['jti'], string_types): - raise JWTClaimsError('JWT ID must be a string.') + if not isinstance(claims["jti"], str): + raise JWTClaimsError("JWT ID must be a string.") def _validate_at_hash(claims, access_token, algorithm): """ Validates that the 'at_hash' is valid. Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string. Use of this claim is OPTIONAL. Args: claims (dict): The claims dictionary to validate. access_token (str): The access token returned by the OpenID Provider. algorithm (str): The algorithm used to sign the JWT, as specified by the token headers. """ - if 'at_hash' not in claims: + if "at_hash" not in claims: return if not access_token: - msg = 'No access_token provided to compare against at_hash claim.' + msg = "No access_token provided to compare against at_hash claim." raise JWTClaimsError(msg) try: - expected_hash = calculate_at_hash(access_token, - ALGORITHMS.HASHES[algorithm]) + expected_hash = calculate_at_hash(access_token, ALGORITHMS.HASHES[algorithm]) except (TypeError, ValueError): - msg = 'Unable to calculate at_hash to verify against token claims.' + msg = "Unable to calculate at_hash to verify against token claims." raise JWTClaimsError(msg) - if claims['at_hash'] != expected_hash: - raise JWTClaimsError('at_hash claim does not match access_token.') + if claims["at_hash"] != expected_hash: + raise JWTClaimsError("at_hash claim does not match access_token.") -def _validate_claims(claims, audience=None, issuer=None, subject=None, - algorithm=None, access_token=None, options=None): +def _validate_claims(claims, audience=None, issuer=None, subject=None, algorithm=None, access_token=None, options=None): - leeway = options.get('leeway', 0) + leeway = options.get("leeway", 0) if isinstance(leeway, timedelta): leeway = timedelta_total_seconds(leeway) - - for require_claim in [ - e[len("require_"):] for e in options.keys() if e.startswith("require_") and options[e] - ]: + required_claims = [e[len("require_") :] for e in options.keys() if e.startswith("require_") and options[e]] + for require_claim in required_claims: if require_claim not in claims: raise JWTError('missing required key "%s" among claims' % require_claim) else: - options['verify_' + require_claim] = True # override verify when required + options["verify_" + require_claim] = True # override verify when required - if not isinstance(audience, (string_types, type(None))): - raise JWTError('audience must be a string or None') + if not isinstance(audience, ((str,), type(None))): + raise JWTError("audience must be a string or None") - if options.get('verify_iat'): + if options.get("verify_iat"): _validate_iat(claims) - if options.get('verify_nbf'): + if options.get("verify_nbf"): _validate_nbf(claims, leeway=leeway) - if options.get('verify_exp'): + if options.get("verify_exp"): _validate_exp(claims, leeway=leeway) - if options.get('verify_aud'): + if options.get("verify_aud"): _validate_aud(claims, audience=audience) - if options.get('verify_iss'): + if options.get("verify_iss"): _validate_iss(claims, issuer=issuer) - if options.get('verify_sub'): + if options.get("verify_sub"): _validate_sub(claims, subject=subject) - if options.get('verify_jti'): + if options.get("verify_jti"): _validate_jti(claims) - if options.get('verify_at_hash'): + if options.get("verify_at_hash"): _validate_at_hash(claims, access_token, algorithm) diff --git a/jose/utils.py b/jose/utils.py index 2b98472..fcef885 100644 --- a/jose/utils.py +++ b/jose/utils.py @@ -1,134 +1,108 @@ - import base64 -import hmac -import six import struct -import sys - -if sys.version_info > (3,): - # Deal with integer compatibilities between Python 2 and 3. - # Using `from builtins import int` is not supported on AppEngine. - long = int - # Piggyback of the backends implementation of the function that converts a long # to a bytes stream. Some plumbing is necessary to have the signatures match. try: - from Crypto.Util.number import long_to_bytes -except ImportError: - try: - from cryptography.utils import int_to_bytes as _long_to_bytes + from cryptography.utils import int_to_bytes as _long_to_bytes - def long_to_bytes(n, blocksize=0): - return _long_to_bytes(n, blocksize or None) + def long_to_bytes(n, blocksize=0): + return _long_to_bytes(n, blocksize or None) - except ImportError: - from ecdsa.ecdsa import int_to_string as _long_to_bytes - def long_to_bytes(n, blocksize=0): - ret = _long_to_bytes(n) - if blocksize == 0: - return ret - else: - assert len(ret) <= blocksize - padding = blocksize - len(ret) - return b'\x00' * padding + ret +except ImportError: + from ecdsa.ecdsa import int_to_string as _long_to_bytes + + def long_to_bytes(n, blocksize=0): + ret = _long_to_bytes(n) + if blocksize == 0: + return ret + else: + assert len(ret) <= blocksize + padding = blocksize - len(ret) + return b"\x00" * padding + ret def long_to_base64(data, size=0): - return base64.urlsafe_b64encode(long_to_bytes(data, size)).strip(b'=') + return base64.urlsafe_b64encode(long_to_bytes(data, size)).strip(b"=") def int_arr_to_long(arr): - return long(''.join(["%02x" % byte for byte in arr]), 16) + return int("".join(["%02x" % byte for byte in arr]), 16) def base64_to_long(data): - if isinstance(data, six.text_type): + if isinstance(data, str): data = data.encode("ascii") # urlsafe_b64decode will happily convert b64encoded data - _d = base64.urlsafe_b64decode(bytes(data) + b'==') - return int_arr_to_long(struct.unpack('%sB' % len(_d), _d)) + _d = base64.urlsafe_b64decode(bytes(data) + b"==") + return int_arr_to_long(struct.unpack("%sB" % len(_d), _d)) def calculate_at_hash(access_token, hash_alg): """Helper method for calculating an access token hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken Its value is the base64url encoding of the left-most half of the hash of the octets of the ASCII representation of the access_token value, where the hash algorithm used is the hash algorithm used in the alg Header Parameter of the ID Token's JOSE Header. For instance, if the alg is RS256, hash the access_token value with SHA-256, then take the left-most 128 bits and base64url encode them. The at_hash value is a case sensitive string. Args: access_token (str): An access token string. hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256 """ - hash_digest = hash_alg(access_token.encode('utf-8')).digest() + hash_digest = hash_alg(access_token.encode("utf-8")).digest() cut_at = int(len(hash_digest) / 2) truncated = hash_digest[:cut_at] at_hash = base64url_encode(truncated) - return at_hash.decode('utf-8') + return at_hash.decode("utf-8") def base64url_decode(input): """Helper method to base64url_decode a string. Args: input (str): A base64url_encoded string to decode. """ rem = len(input) % 4 if rem > 0: - input += b'=' * (4 - rem) + input += b"=" * (4 - rem) return base64.urlsafe_b64decode(input) def base64url_encode(input): """Helper method to base64url_encode a string. Args: input (str): A base64url_encoded string to encode. """ - return base64.urlsafe_b64encode(input).replace(b'=', b'') + return base64.urlsafe_b64encode(input).replace(b"=", b"") def timedelta_total_seconds(delta): """Helper method to determine the total number of seconds from a timedelta. Args: delta (timedelta): A timedelta to convert to seconds. """ return delta.days * 24 * 60 * 60 + delta.seconds -def constant_time_string_compare(a, b): - """Helper for comparing string in constant time, independent - of the python version being used. - - Args: - a (str): A string to compare - b (str): A string to compare - """ - - try: - return hmac.compare_digest(a, b) - except AttributeError: - - if len(a) != len(b): - return False - - result = 0 - - for x, y in zip(a, b): - result |= ord(x) ^ ord(y) +def ensure_binary(s): + """Coerce **s** to bytes.""" - return result == 0 + if isinstance(s, bytes): + return s + if isinstance(s, str): + return s.encode("utf-8", "strict") + raise TypeError(f"not expecting type '{type(s)}'") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f95be1d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 120 + + +[tool.black] +line-length = 120 +target-version = ["py38"] \ No newline at end of file diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..03589cf --- /dev/null +++ b/pytest.ini @@ -0,0 +1,7 @@ +[pytest] +markers = + pycrypto: marks tests as applicable with PyCrypto backend + pycryptodome: marks tests as applicable with PyCryptodome backend + ecdsa: marks tests as applicable with ecdsa backend + cryptography: marks tests as applicable with cryptography backend + backend_compatibility: mark tests as testing compatibility between backends diff --git a/python_jose.egg-info/PKG-INFO b/python_jose.egg-info/PKG-INFO deleted file mode 100644 index 0f4788b..0000000 --- a/python_jose.egg-info/PKG-INFO +++ /dev/null @@ -1,136 +0,0 @@ -Metadata-Version: 1.1 -Name: python-jose -Version: 3.1.0 -Summary: JOSE implementation in Python -Home-page: http://github.com/mpdavis/python-jose -Author: Michael Davis -Author-email: mike.philip.davis@gmail.com -License: MIT -Description: python-jose - =========== - - A JOSE implementation in Python - - |Build Status| |Coverage Status| |Docs| - - Docs are available on ReadTheDocs_. - - The JavaScript Object Signing and Encryption (JOSE) technologies - JSON - Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and - JSON Web Algorithms (JWA) - collectively can be used to encrypt and/or - sign content using a variety of algorithms. While the full set of - permutations is extremely large, and might be daunting to some, it is - expected that most applications will only use a small set of algorithms - to meet their needs. - - - Installation - ------------ - - :: - - $ pip install python-jose[cryptography] - - - Cryptographic Backends - ---------------------- - - As of 3.1.0, python-jose implements four different cryptographic backends. - The backend must be selected as an extra when installing python-jose. - If you do not select a backend, the native-python backend will be installed. - - Unless otherwise noted, all backends support all operations. - - Due to complexities with setuptools, the native-python backend is always installed, - even if you select a different backend on install. - We recommend that you remove unnecessary dependencies in production. - - #. cryptography - - * This backend uses `pyca/cryptography`_ for all cryptographic operations. - This is the recommended backend and is selected over all other backends if any others are present. - * Installation: ``pip install python-jose[cryptography]`` - * Unused dependencies: - - * ``rsa`` - * ``ecdsa`` - * ``pyasn1`` - - #. pycryptodome - - * This backend uses `pycryptodome`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycryptodome]`` - * Unused dependencies: - - * ``rsa`` - - #. native-python - - * This backend uses `python-rsa`_ and `python-ecdsa`_ for all cryptographic operations. - This backend is always installed but any other backend will take precedence if one is installed. - * Installation: ``pip install python-jose`` - - .. note:: - - The native-python backend cannot process certificates. - - #. pycrypto - - * This backend uses `pycrypto`_ for all cryptographic operations. - * Installation: ``pip install python-jose[pycrypto]`` - * Unused dependencies: - - * ``rsa`` - - .. warning:: - - The `pycrypto`_ project has not been maintained since 2013. - This backend is maintained for legacy compatibility purposes only. - Do not use this backend unless you cannot use any of the others. - - Usage - ----- - - .. code-block:: python - - >>> from jose import jwt - >>> token = jwt.encode({'key': 'value'}, 'secret', algorithm='HS256') - u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.FG-8UppwHaFp1LgRYQQeS6EDQF7_6-bMFegNucHjmWg' - - >>> jwt.decode(token, 'secret', algorithms=['HS256']) - {u'key': u'value'} - - - Thanks - ------ - - This library was originally based heavily on the work of the folks over at PyJWT_. - - .. |Build Status| image:: https://travis-ci.org/mpdavis/python-jose.svg?branch=master - :target: https://travis-ci.org/mpdavis/python-jose - .. |Coverage Status| image:: http://codecov.io/github/mpdavis/python-jose/coverage.svg?branch=master - :target: http://codecov.io/github/mpdavis/python-jose?branch=master - .. |Docs| image:: https://readthedocs.org/projects/python-jose/badge/ - :target: https://python-jose.readthedocs.org/en/latest/ - .. _ReadTheDocs: https://python-jose.readthedocs.org/en/latest/ - .. _PyJWT: https://github.com/jpadilla/pyjwt - .. _pyca/cryptography: http://cryptography.io/ - .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/ - .. _pycrypto: https://www.dlitz.net/software/pycrypto/ - .. _python-ecdsa: https://github.com/warner/python-ecdsa - .. _python-rsa: https://stuvel.eu/rsa - -Keywords: jose jws jwe jwt json web token security signing -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: Natural Language :: English -Classifier: License :: OSI Approved :: MIT License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Utilities diff --git a/python_jose.egg-info/SOURCES.txt b/python_jose.egg-info/SOURCES.txt deleted file mode 100644 index 4456b12..0000000 --- a/python_jose.egg-info/SOURCES.txt +++ /dev/null @@ -1,103 +0,0 @@ -LICENSE -MANIFEST.in -README.rst -requirements-dev.txt -requirements-rtd.txt -requirements.txt -setup.cfg -setup.py -tox.ini -docs/Makefile -docs/conf.py -docs/index.rst -docs/make.bat -docs/jwk/api.rst -docs/jwk/index.rst -docs/jws/api.rst -docs/jws/index.rst -docs/jwt/api.rst -docs/jwt/index.rst -jose/__init__.py -jose/constants.py -jose/exceptions.py -jose/jwk.py -jose/jws.py -jose/jwt.py -jose/utils.py -jose/backends/__init__.py -jose/backends/_asn1.py -jose/backends/base.py -jose/backends/cryptography_backend.py -jose/backends/ecdsa_backend.py -jose/backends/pycrypto_backend.py -jose/backends/rsa_backend.py -python_jose.egg-info/PKG-INFO -python_jose.egg-info/SOURCES.txt -python_jose.egg-info/dependency_links.txt -python_jose.egg-info/requires.txt -python_jose.egg-info/top_level.txt -tests/__init__.py -tests/__init__.pyc -tests/test_asn1.py -tests/test_backends.py -tests/test_firebase.py -tests/test_jwk.py -tests/test_jws.py -tests/test_jwt.py -tests/test_utils.py -tests/__pycache__/__init__.cpython-34.pyc -tests/__pycache__/__init__.cpython-36.pyc -tests/__pycache__/test_firebase.cpython-27-PYTEST.pyc -tests/__pycache__/test_firebase.cpython-34-PYTEST.pyc -tests/__pycache__/test_firebase.cpython-36-PYTEST.pyc -tests/__pycache__/test_jwk.cpython-27-PYTEST.pyc -tests/__pycache__/test_jwk.cpython-34-PYTEST.pyc -tests/__pycache__/test_jwk.cpython-36-PYTEST.pyc -tests/__pycache__/test_jws.cpython-27-PYTEST.pyc -tests/__pycache__/test_jws.cpython-34-PYTEST.pyc -tests/__pycache__/test_jws.cpython-36-PYTEST.pyc -tests/__pycache__/test_jwt.cpython-27-PYTEST.pyc -tests/__pycache__/test_jwt.cpython-34-PYTEST.pyc -tests/__pycache__/test_jwt.cpython-36-PYTEST.pyc -tests/__pycache__/test_tmp.cpython-36-PYTEST.pyc -tests/__pycache__/test_utils.cpython-27-PYTEST.pyc -tests/__pycache__/test_utils.cpython-34-PYTEST.pyc -tests/__pycache__/test_utils.cpython-36-PYTEST.pyc -tests/algorithms/__init__.py -tests/algorithms/__init__.pyc -tests/algorithms/test_EC.py -tests/algorithms/test_EC_compat.py -tests/algorithms/test_HMAC.py -tests/algorithms/test_RSA.py -tests/algorithms/test_RSA_compat.py -tests/algorithms/test_base.py -tests/algorithms/__pycache__/__init__.cpython-34.pyc -tests/algorithms/__pycache__/__init__.cpython-36.pyc -tests/algorithms/__pycache__/test_EC.cpython-27-PYTEST.pyc -tests/algorithms/__pycache__/test_EC.cpython-34-PYTEST.pyc -tests/algorithms/__pycache__/test_EC.cpython-36-PYTEST.pyc -tests/algorithms/__pycache__/test_Ed25519.cpython-27-PYTEST.pyc -tests/algorithms/__pycache__/test_Ed25519.cpython-34-PYTEST.pyc -tests/algorithms/__pycache__/test_Ed25519.cpython-36-PYTEST.pyc -tests/algorithms/__pycache__/test_HMAC.cpython-27-PYTEST.pyc -tests/algorithms/__pycache__/test_HMAC.cpython-34-PYTEST.pyc -tests/algorithms/__pycache__/test_HMAC.cpython-36-PYTEST.pyc -tests/algorithms/__pycache__/test_RSA.cpython-27-PYTEST.pyc -tests/algorithms/__pycache__/test_RSA.cpython-34-PYTEST.pyc -tests/algorithms/__pycache__/test_RSA.cpython-36-PYTEST.pyc -tests/algorithms/__pycache__/test_base.cpython-27-PYTEST.pyc -tests/algorithms/__pycache__/test_base.cpython-34-PYTEST.pyc -tests/algorithms/__pycache__/test_base.cpython-36-PYTEST.pyc -tests/rfc/__init__.py -tests/rfc/__init__.pyc -tests/rfc/test_rfc7520.py -tests/rfc/test_rfc8037.py -tests/rfc/test_rfc8410.py -tests/rfc/__pycache__/__init__.cpython-34.pyc -tests/rfc/__pycache__/__init__.cpython-36.pyc -tests/rfc/__pycache__/test_rfc7520.cpython-27-PYTEST.pyc -tests/rfc/__pycache__/test_rfc7520.cpython-34-PYTEST.pyc -tests/rfc/__pycache__/test_rfc7520.cpython-36-PYTEST.pyc -tests/rfc/__pycache__/test_rfc8037.cpython-27-PYTEST.pyc -tests/rfc/__pycache__/test_rfc8037.cpython-34-PYTEST.pyc -tests/rfc/__pycache__/test_rfc8037.cpython-36-PYTEST.pyc \ No newline at end of file diff --git a/python_jose.egg-info/dependency_links.txt b/python_jose.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/python_jose.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/python_jose.egg-info/requires.txt b/python_jose.egg-info/requires.txt deleted file mode 100644 index 3c30d39..0000000 --- a/python_jose.egg-info/requires.txt +++ /dev/null @@ -1,15 +0,0 @@ -six <2.0 -ecdsa <1.0 -rsa -pyasn1 - -[cryptography] -cryptography - -[pycrypto] -pycrypto >=2.6.0, <2.7.0 -pyasn1 - -[pycryptodome] -pycryptodome >=3.3.1, <4.0.0 -pyasn1 diff --git a/python_jose.egg-info/top_level.txt b/python_jose.egg-info/top_level.txt deleted file mode 100644 index 3ac440a..0000000 --- a/python_jose.egg-info/top_level.txt +++ /dev/null @@ -1,2 +0,0 @@ -jose -jose/backends diff --git a/requirements-dev.txt b/requirements-dev.txt index 495b2d3..6ddd022 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,14 +1,9 @@ -PyYAML==3.11 +PyYAML==5.4.1 cov-core==1.15.0 -coverage==4.4 +coverage==5.5 coveralls==1.5.1 -cryptography==2.4.2 +cryptography==3.4.7 docopt==0.6.2 -nose==1.3.6 -py==1.5.4 -pytest==4.1.1 -pytest-cov==2.6.1 -# wsgiref is included in python standard library in Python 3, and will fail to install. -wsgiref==0.1.2; python_version < "3.0" --r requirements.txt --r requirements-rtd.txt +pytest==6.2.3 +pytest-cov==2.11.1 +-r requirements.txt \ No newline at end of file diff --git a/requirements-rtd.txt b/requirements-rtd.txt index 36792c2..e69de29 100644 --- a/requirements-rtd.txt +++ b/requirements-rtd.txt @@ -1 +0,0 @@ -sphinxcontrib-napoleon==0.3.4 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 04dd5d1..7bc375f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ pycryptodome -six rsa -ecdsa +ecdsa != 0.15 pyasn1 diff --git a/setup.cfg b/setup.cfg index 156e77e..4eacddf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,19 +1,17 @@ +[metadata] +version = attr: jose.__version__ + [flake8] -max-line-length = 119 +max-line-length = 120 +ignore = E203,W503 [wheel] universal = 1 [aliases] -test = pytest +test=pytest [tool:pytest] addopts = --cov-report term-missing --cov jose testpaths = tests python_files = test_*.py - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - diff --git a/setup.py b/setup.py index ed196b8..36411db 100644 --- a/setup.py +++ b/setup.py @@ -1,83 +1,76 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import os -import platform - -import jose +from pathlib import Path from setuptools import setup +import jose # noqa: F401 -with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: - long_description = readme.read() +long_description = (Path(__file__).parent / "README.rst").read_text() def get_packages(package): """ Return root package and all sub-packages. """ return [ dirpath for dirpath, dirnames, filenames in os.walk(package) - if os.path.exists(os.path.join(dirpath, '__init__.py')) + if os.path.exists(os.path.join(dirpath, "__init__.py")) ] -def _cryptography_version(): - # pyca/cryptography dropped support for PyPy < 5.4 in 2.5 - # https://cryptography.io/en/latest/changelog/#v2-5 - if platform.python_implementation() == 'PyPy' and platform.python_version() < '5.4': - return 'cryptography < 2.5' - - return 'cryptography' - - -pyasn1 = ['pyasn1'] +pyasn1 = ["pyasn1"] extras_require = { - 'cryptography': [_cryptography_version()], - 'pycrypto': ['pycrypto >=2.6.0, <2.7.0'] + pyasn1, - 'pycryptodome': ['pycryptodome >=3.3.1, <4.0.0'] + pyasn1, + "cryptography": ["cryptography>=3.4.0"], + "pycrypto": ["pycrypto >=2.6.0, <2.7.0"] + pyasn1, + "pycryptodome": ["pycryptodome >=3.3.1, <4.0.0"] + pyasn1, } -legacy_backend_requires = ['ecdsa <1.0', 'rsa'] + pyasn1 -install_requires = ['six <2.0'] - # TODO: work this into the extras selection instead. -install_requires += legacy_backend_requires +install_requires = ["ecdsa != 0.15", "rsa"] + pyasn1 setup( - name='python-jose', - version=jose.__version__, - author='Michael Davis', - author_email='mike.philip.davis@gmail.com', - description='JOSE implementation in Python', - license='MIT', - keywords='jose jws jwe jwt json web token security signing', - url='http://github.com/mpdavis/python-jose', - packages=get_packages('jose'), + name="python-jose", + author="Michael Davis", + author_email="mike.philip.davis@gmail.com", + description="JOSE implementation in Python", + license="MIT", + keywords="jose jws jwe jwt json web token security signing", + url="http://github.com/mpdavis/python-jose", + packages=get_packages("jose"), long_description=long_description, + project_urls={ + "Documentation": "https://python-jose.readthedocs.io/en/latest/", + "Source": "https://github.com/mpdavis/python-jose/", + "Tracker": "https://github.com/mpdavis/python-jose/issues/", + "Changelog": "https://github.com/mpdavis/python-jose/blob/master/CHANGELOG.md", + }, classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Utilities', + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Utilities", ], extras_require=extras_require, - setup_requires=['pytest-runner'], + setup_requires=[ + "pytest-runner", + "setuptools>=39.2.0", + ], tests_require=[ - 'six', - 'ecdsa', - 'pytest', - 'pytest-cov', - 'pytest-runner', + "ecdsa != 0.15", + "pytest", + "pytest-cov", + "pytest-runner", ], - install_requires=install_requires + install_requires=install_requires, ) diff --git a/tests/__init__.pyc b/tests/__init__.pyc deleted file mode 100644 index d34bd92..0000000 Binary files a/tests/__init__.pyc and /dev/null differ diff --git a/tests/__pycache__/__init__.cpython-34.pyc b/tests/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index 16b0b3b..0000000 Binary files a/tests/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/tests/__pycache__/__init__.cpython-36.pyc b/tests/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 5ef2520..0000000 Binary files a/tests/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/tests/__pycache__/test_firebase.cpython-27-PYTEST.pyc b/tests/__pycache__/test_firebase.cpython-27-PYTEST.pyc deleted file mode 100644 index 0395c13..0000000 Binary files a/tests/__pycache__/test_firebase.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_firebase.cpython-34-PYTEST.pyc b/tests/__pycache__/test_firebase.cpython-34-PYTEST.pyc deleted file mode 100644 index 619e418..0000000 Binary files a/tests/__pycache__/test_firebase.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_firebase.cpython-36-PYTEST.pyc b/tests/__pycache__/test_firebase.cpython-36-PYTEST.pyc deleted file mode 100644 index 72c0de7..0000000 Binary files a/tests/__pycache__/test_firebase.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwk.cpython-27-PYTEST.pyc b/tests/__pycache__/test_jwk.cpython-27-PYTEST.pyc deleted file mode 100644 index 9f0b61e..0000000 Binary files a/tests/__pycache__/test_jwk.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwk.cpython-34-PYTEST.pyc b/tests/__pycache__/test_jwk.cpython-34-PYTEST.pyc deleted file mode 100644 index b0698ca..0000000 Binary files a/tests/__pycache__/test_jwk.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwk.cpython-36-PYTEST.pyc b/tests/__pycache__/test_jwk.cpython-36-PYTEST.pyc deleted file mode 100644 index 9bdd09e..0000000 Binary files a/tests/__pycache__/test_jwk.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jws.cpython-27-PYTEST.pyc b/tests/__pycache__/test_jws.cpython-27-PYTEST.pyc deleted file mode 100644 index 8c58381..0000000 Binary files a/tests/__pycache__/test_jws.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jws.cpython-34-PYTEST.pyc b/tests/__pycache__/test_jws.cpython-34-PYTEST.pyc deleted file mode 100644 index 466bd91..0000000 Binary files a/tests/__pycache__/test_jws.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jws.cpython-36-PYTEST.pyc b/tests/__pycache__/test_jws.cpython-36-PYTEST.pyc deleted file mode 100644 index d81f49a..0000000 Binary files a/tests/__pycache__/test_jws.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwt.cpython-27-PYTEST.pyc b/tests/__pycache__/test_jwt.cpython-27-PYTEST.pyc deleted file mode 100644 index 0e59a37..0000000 Binary files a/tests/__pycache__/test_jwt.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwt.cpython-34-PYTEST.pyc b/tests/__pycache__/test_jwt.cpython-34-PYTEST.pyc deleted file mode 100644 index c74a003..0000000 Binary files a/tests/__pycache__/test_jwt.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_jwt.cpython-36-PYTEST.pyc b/tests/__pycache__/test_jwt.cpython-36-PYTEST.pyc deleted file mode 100644 index c3c4e2f..0000000 Binary files a/tests/__pycache__/test_jwt.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_tmp.cpython-36-PYTEST.pyc b/tests/__pycache__/test_tmp.cpython-36-PYTEST.pyc deleted file mode 100644 index c90e07b..0000000 Binary files a/tests/__pycache__/test_tmp.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_utils.cpython-27-PYTEST.pyc b/tests/__pycache__/test_utils.cpython-27-PYTEST.pyc deleted file mode 100644 index 1c8958b..0000000 Binary files a/tests/__pycache__/test_utils.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_utils.cpython-34-PYTEST.pyc b/tests/__pycache__/test_utils.cpython-34-PYTEST.pyc deleted file mode 100644 index 7305868..0000000 Binary files a/tests/__pycache__/test_utils.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/__pycache__/test_utils.cpython-36-PYTEST.pyc b/tests/__pycache__/test_utils.cpython-36-PYTEST.pyc deleted file mode 100644 index abcec0f..0000000 Binary files a/tests/__pycache__/test_utils.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__init__.pyc b/tests/algorithms/__init__.pyc deleted file mode 100644 index 0fa9dbd..0000000 Binary files a/tests/algorithms/__init__.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/__init__.cpython-34.pyc b/tests/algorithms/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index af1a9e4..0000000 Binary files a/tests/algorithms/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/__init__.cpython-36.pyc b/tests/algorithms/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 31b3fb9..0000000 Binary files a/tests/algorithms/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_EC.cpython-27-PYTEST.pyc b/tests/algorithms/__pycache__/test_EC.cpython-27-PYTEST.pyc deleted file mode 100644 index 94115e5..0000000 Binary files a/tests/algorithms/__pycache__/test_EC.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_EC.cpython-34-PYTEST.pyc b/tests/algorithms/__pycache__/test_EC.cpython-34-PYTEST.pyc deleted file mode 100644 index bcbc34b..0000000 Binary files a/tests/algorithms/__pycache__/test_EC.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_EC.cpython-36-PYTEST.pyc b/tests/algorithms/__pycache__/test_EC.cpython-36-PYTEST.pyc deleted file mode 100644 index bbac5f9..0000000 Binary files a/tests/algorithms/__pycache__/test_EC.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_Ed25519.cpython-27-PYTEST.pyc b/tests/algorithms/__pycache__/test_Ed25519.cpython-27-PYTEST.pyc deleted file mode 100644 index f9e55a9..0000000 Binary files a/tests/algorithms/__pycache__/test_Ed25519.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_Ed25519.cpython-34-PYTEST.pyc b/tests/algorithms/__pycache__/test_Ed25519.cpython-34-PYTEST.pyc deleted file mode 100644 index f3e7a4f..0000000 Binary files a/tests/algorithms/__pycache__/test_Ed25519.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_Ed25519.cpython-36-PYTEST.pyc b/tests/algorithms/__pycache__/test_Ed25519.cpython-36-PYTEST.pyc deleted file mode 100644 index 2f1adcf..0000000 Binary files a/tests/algorithms/__pycache__/test_Ed25519.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_HMAC.cpython-27-PYTEST.pyc b/tests/algorithms/__pycache__/test_HMAC.cpython-27-PYTEST.pyc deleted file mode 100644 index 69da1b6..0000000 Binary files a/tests/algorithms/__pycache__/test_HMAC.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_HMAC.cpython-34-PYTEST.pyc b/tests/algorithms/__pycache__/test_HMAC.cpython-34-PYTEST.pyc deleted file mode 100644 index 2e1a3ee..0000000 Binary files a/tests/algorithms/__pycache__/test_HMAC.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_HMAC.cpython-36-PYTEST.pyc b/tests/algorithms/__pycache__/test_HMAC.cpython-36-PYTEST.pyc deleted file mode 100644 index 8923400..0000000 Binary files a/tests/algorithms/__pycache__/test_HMAC.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_RSA.cpython-27-PYTEST.pyc b/tests/algorithms/__pycache__/test_RSA.cpython-27-PYTEST.pyc deleted file mode 100644 index 17c57f6..0000000 Binary files a/tests/algorithms/__pycache__/test_RSA.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_RSA.cpython-34-PYTEST.pyc b/tests/algorithms/__pycache__/test_RSA.cpython-34-PYTEST.pyc deleted file mode 100644 index 6833a7d..0000000 Binary files a/tests/algorithms/__pycache__/test_RSA.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_RSA.cpython-36-PYTEST.pyc b/tests/algorithms/__pycache__/test_RSA.cpython-36-PYTEST.pyc deleted file mode 100644 index f2ede44..0000000 Binary files a/tests/algorithms/__pycache__/test_RSA.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_base.cpython-27-PYTEST.pyc b/tests/algorithms/__pycache__/test_base.cpython-27-PYTEST.pyc deleted file mode 100644 index 0ca6e9b..0000000 Binary files a/tests/algorithms/__pycache__/test_base.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_base.cpython-34-PYTEST.pyc b/tests/algorithms/__pycache__/test_base.cpython-34-PYTEST.pyc deleted file mode 100644 index 85b8c68..0000000 Binary files a/tests/algorithms/__pycache__/test_base.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/__pycache__/test_base.cpython-36-PYTEST.pyc b/tests/algorithms/__pycache__/test_base.cpython-36-PYTEST.pyc deleted file mode 100644 index 7fa239b..0000000 Binary files a/tests/algorithms/__pycache__/test_base.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/algorithms/test_AES.py b/tests/algorithms/test_AES.py new file mode 100644 index 0000000..9d06017 --- /dev/null +++ b/tests/algorithms/test_AES.py @@ -0,0 +1,59 @@ +from binascii import hexlify, unhexlify + +import pytest + +from jose.constants import ALGORITHMS + +try: + from jose.backends.pycrypto_backend import AESKey as PyCryptoAESKey +except ImportError as e: + PyCryptoAESKey = None + +try: + from jose.backends.cryptography_backend import CryptographyAESKey +except ImportError as e: + CryptographyAESKey = None + + +# List of Tuple of (alg, key, kek, wrapped) obtained from +# https://tools.ietf.org/html/rfc3394#section-2.2.3.1 +VECTORS = ( + ( + ALGORITHMS.A128KW, + b"00112233445566778899AABBCCDDEEFF", + b"000102030405060708090A0B0C0D0E0F", + b"1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5", + ), + ( + ALGORITHMS.A192KW, + b"00112233445566778899AABBCCDDEEFF0001020304050607", + b"000102030405060708090A0B0C0D0E0F1011121314151617", + b"031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2", + ), + ( + ALGORITHMS.A256KW, + b"00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F", + b"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F", + b"28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21", + ), +) + + +@pytest.mark.cryptography +@pytest.mark.skipif(CryptographyAESKey is None, reason="Cryptography backend not available") +class TestCryptographyAesKeywrap: + @pytest.mark.parametrize("alg,hex_key,hex_kek,expected", VECTORS) + def test_wrap(self, alg, hex_key, hex_kek, expected): + bin_key = unhexlify(hex_key) + bin_kek = unhexlify(hex_kek) + aes_key = CryptographyAESKey(bin_kek, alg) + actual = hexlify(aes_key.wrap_key(bin_key)).upper() + assert actual == expected + + @pytest.mark.parametrize("alg,expected,hex_kek,hex_wrapped", VECTORS) + def test_unwrap(self, alg, expected, hex_kek, hex_wrapped): + bin_kek = unhexlify(hex_kek) + bin_wrapped = unhexlify(hex_wrapped) + aes_key = CryptographyAESKey(bin_kek, alg) + actual = hexlify(aes_key.unwrap_key(bin_wrapped)).upper() + assert actual == expected diff --git a/tests/algorithms/test_AES_compat.py b/tests/algorithms/test_AES_compat.py new file mode 100644 index 0000000..4f05bed --- /dev/null +++ b/tests/algorithms/test_AES_compat.py @@ -0,0 +1,66 @@ +import pytest + +try: + from jose.backends.cryptography_backend import CryptographyAESKey +except ImportError: + CryptographyAESKey = None + +from jose.constants import ALGORITHMS +from jose.exceptions import JWEError + +CRYPTO_BACKENDS = (pytest.param(CryptographyAESKey, id="pyca/cryptography"),) + + +@pytest.mark.backend_compatibility +@pytest.mark.skipif( + CryptographyAESKey is None, reason="Multiple crypto backends not available for backend compatibility tests" +) +class TestBackendAesCompatibility: + @pytest.mark.parametrize("backend_decrypt", CRYPTO_BACKENDS) + @pytest.mark.parametrize("backend_encrypt", CRYPTO_BACKENDS) + @pytest.mark.parametrize("algorithm", ALGORITHMS.AES_PSEUDO) + def test_encryption_parity(self, backend_encrypt, backend_decrypt, algorithm): + if "128" in algorithm: + key = b"8slRzzty6dKMaFCP" + elif "192" in algorithm: + key = b"8slRzzty6dKMaFCP8slRzzty" + else: + key = b"8slRzzty6dKMaFCP8slRzzty6dKMaFCP" + + key_encrypt = backend_encrypt(key, algorithm) + key_decrypt = backend_decrypt(key, algorithm) + plain_text = b"test" + aad = b"extra data" if "GCM" in algorithm else None + + iv, cipher_text, tag = key_encrypt.encrypt(plain_text, aad) + + # verify decrypt to original plain text + actual = key_decrypt.decrypt(cipher_text, iv, aad, tag) + assert actual == plain_text + + with pytest.raises(JWEError): + key_decrypt.decrypt(b"n" * 64) + + @pytest.mark.parametrize("backend_key_wrap", CRYPTO_BACKENDS) + @pytest.mark.parametrize("backend_key_unwrap", CRYPTO_BACKENDS) + @pytest.mark.parametrize("algorithm", ALGORITHMS.AES_KW) + def test_wrap_parity(self, backend_key_wrap, backend_key_unwrap, algorithm): + if "128" in algorithm: + key = b"8slRzzty6dKMaFCP" + elif "192" in algorithm: + key = b"8slRzzty6dKMaFCP8slRzzty" + else: + key = b"8slRzzty6dKMaFCP8slRzzty6dKMaFCP" + + key_wrap = backend_key_wrap(key, algorithm) + key_unwrap = backend_key_unwrap(key, algorithm) + plain_text = b"sixteen byte key" + + wrapped_key = key_wrap.wrap_key(plain_text) + + # verify unwrap_key to original plain text + actual = key_unwrap.unwrap_key(wrapped_key) + assert actual == plain_text + + with pytest.raises(JWEError): + key_unwrap.decrypt(b"n" * 64) diff --git a/tests/algorithms/test_EC.py b/tests/algorithms/test_EC.py index 7f012af..6c167d2 100644 --- a/tests/algorithms/test_EC.py +++ b/tests/algorithms/test_EC.py @@ -1,200 +1,199 @@ +import json +from jose.backends import ECKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError -from jose.backends import ECKey try: - from jose.backends.ecdsa_backend import ECDSAECKey import ecdsa + + from jose.backends.ecdsa_backend import ECDSAECKey except ImportError: ECDSAECKey = ecdsa = None try: - from jose.backends.cryptography_backend import CryptographyECKey - from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc from cryptography.hazmat.backends import default_backend as CryptographyBackend + from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc + + from jose.backends.cryptography_backend import CryptographyECKey except ImportError: CryptographyECKey = CryptographyEc = CryptographyBackend = None import pytest private_key = """-----BEGIN EC PRIVATE KEY----- MHcCAQEEIOiSs10XnBlfykk5zsJRmzYybKdMlGniSJcssDvUcF6DoAoGCCqGSM49 AwEHoUQDQgAE7gb4edKJ7ul9IgomCdcOebQTZ8qktqtBfRKboa71CfEKzBruUi+D WkG0HJWIORlPbvXME+DRh6G/yVOKnTm88Q== -----END EC PRIVATE KEY-----""" # Private key generated using NIST256p curve TOO_SHORT_PRIVATE_KEY = b"""\ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIMlUyYGOpjV4bbW0C9FKS2zkspD0L/5vJLnr6sJoLdc+oAoGCCqGSM49 AwEHoUQDQgAE6TDUNj5QXl+RKdZvBV+cg7Td6cJRB+Ta8XAhIuCAzonq0Ix//1+C pNSsy11sIKmMl61YJzxvZ6WkNluBmkDPCQ== -----END EC PRIVATE KEY----- """ # ES256 signatures generated to test conversion logic DER_SIGNATURE = ( b"0F\x02!\x00\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3" b"\x9d\xdf \xd1\xbc\xedK\x01\x87:}\xf2\x02!\x00\xb2shTA\x00\x1a\x13~\xba" b"J\xdb\xeem\x12\x1e\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e" ) RAW_SIGNATURE = ( b"\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3\x9d\xdf " b"\xd1\xbc\xedK\x01\x87:}\xf2\xb2shTA\x00\x1a\x13~\xbaJ\xdb\xeem\x12\x1e" b"\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e" ) def _backend_exception_types(): """Build the backend exception types based on available backends.""" if None not in (ECDSAECKey, ecdsa): yield ECDSAECKey, ecdsa.BadDigestError if CryptographyECKey is not None: yield CryptographyECKey, TypeError @pytest.mark.ecdsa -@pytest.mark.skipif( - None in (ECDSAECKey, ecdsa), - reason="python-ecdsa backend not available" -) +@pytest.mark.skipif(None in (ECDSAECKey, ecdsa), reason="python-ecdsa backend not available") def test_key_from_ecdsa(): key = ecdsa.SigningKey.from_pem(private_key) assert not ECKey(key, ALGORITHMS.ES256).is_public() @pytest.mark.cryptography @pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") -@pytest.mark.parametrize("algorithm, expected_length", ( - (ALGORITHMS.ES256, 32), - (ALGORITHMS.ES384, 48), - (ALGORITHMS.ES512, 66) -)) +@pytest.mark.parametrize( + "algorithm, expected_length", ((ALGORITHMS.ES256, 32), (ALGORITHMS.ES384, 48), (ALGORITHMS.ES512, 66)) +) def test_cryptography_sig_component_length(algorithm, expected_length): # Put mapping inside here to avoid more complex handling for test runs that do not have pyca/cryptography mapping = { ALGORITHMS.ES256: CryptographyEc.SECP256R1, ALGORITHMS.ES384: CryptographyEc.SECP384R1, ALGORITHMS.ES512: CryptographyEc.SECP521R1, } key = CryptographyECKey( - CryptographyEc.generate_private_key(mapping[algorithm](), backend=CryptographyBackend()), - algorithm + CryptographyEc.generate_private_key(mapping[algorithm](), backend=CryptographyBackend()), algorithm ) assert key._sig_component_length() == expected_length @pytest.mark.cryptography @pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") def test_cryptograhy_der_to_raw(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) assert key._der_to_raw(DER_SIGNATURE) == RAW_SIGNATURE @pytest.mark.cryptography @pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available") def test_cryptograhy_raw_to_der(): key = CryptographyECKey(private_key, ALGORITHMS.ES256) assert key._raw_to_der(RAW_SIGNATURE) == DER_SIGNATURE class TestECAlgorithm: - def test_key_from_pem(self): assert not ECKey(private_key, ALGORITHMS.ES256).is_public() def test_to_pem(self): key = ECKey(private_key, ALGORITHMS.ES256) assert not key.is_public() - assert key.to_pem().strip() == private_key.strip().encode('utf-8') + assert key.to_pem().strip() == private_key.strip().encode("utf-8") public_pem = key.public_key().to_pem() assert ECKey(public_pem, ALGORITHMS.ES256).is_public() @pytest.mark.parametrize("Backend,ExceptionType", _backend_exception_types()) def test_key_too_short(self, Backend, ExceptionType): key = Backend(TOO_SHORT_PRIVATE_KEY, ALGORITHMS.ES512) with pytest.raises(ExceptionType): - key.sign(b'foo') + key.sign(b"foo") def test_get_public_key(self): key = ECKey(private_key, ALGORITHMS.ES256) pubkey = key.public_key() pubkey2 = pubkey.public_key() assert pubkey == pubkey2 def test_string_secret(self): - key = 'secret' + key = "secret" with pytest.raises(JOSEError): ECKey(key, ALGORITHMS.ES256) def test_object(self): key = object() with pytest.raises(JOSEError): ECKey(key, ALGORITHMS.ES256) def test_invalid_algorithm(self): with pytest.raises(JWKError): - ECKey(private_key, 'nonexistent') + ECKey(private_key, "nonexistent") with pytest.raises(JWKError): - ECKey({'kty': 'bla'}, ALGORITHMS.ES256) + ECKey({"kty": "bla"}, ALGORITHMS.ES256) def test_EC_jwk(self): key = { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt", } assert not ECKey(key, ALGORITHMS.ES512).is_public() - del key['d'] + del key["d"] # We are now dealing with a public key. assert ECKey(key, ALGORITHMS.ES512).is_public() - del key['x'] + del key["x"] # This key is missing a required parameter. with pytest.raises(JWKError): ECKey(key, ALGORITHMS.ES512) def test_verify(self): key = ECKey(private_key, ALGORITHMS.ES256) - msg = b'test' + msg = b"test" signature = key.sign(msg) public_key = key.public_key() assert bool(public_key.verify(msg, signature)) - assert not bool(public_key.verify(msg, b'not a signature')) + assert not bool(public_key.verify(msg, b"not a signature")) def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) # Public parameters should always be there. - assert 'x' in as_dict - assert 'y' in as_dict - assert 'crv' in as_dict + assert "x" in as_dict + assert "y" in as_dict + assert "crv" in as_dict - assert 'kty' in as_dict - assert as_dict['kty'] == 'EC' + assert "kty" in as_dict + assert as_dict["kty"] == "EC" if private: # Private parameters as well - assert 'd' in as_dict + assert "d" in as_dict else: # Private parameters should be absent - assert 'd' not in as_dict + assert "d" not in as_dict + + # as_dict should be serializable to JSON + json.dumps(as_dict) def test_to_dict(self): key = ECKey(private_key, ALGORITHMS.ES256) self.assert_parameters(key.to_dict(), private=True) self.assert_parameters(key.public_key().to_dict(), private=False) diff --git a/tests/algorithms/test_EC_compat.py b/tests/algorithms/test_EC_compat.py index 1fc6dab..05d033c 100644 --- a/tests/algorithms/test_EC_compat.py +++ b/tests/algorithms/test_EC_compat.py @@ -1,72 +1,71 @@ import pytest try: - from jose.backends.ecdsa_backend import ECDSAECKey from jose.backends.cryptography_backend import CryptographyECKey + from jose.backends.ecdsa_backend import ECDSAECKey except ImportError: ECDSAECKey = CryptographyECKey = None from jose.constants import ALGORITHMS from .test_EC import private_key @pytest.mark.backend_compatibility @pytest.mark.skipif( None in (ECDSAECKey, CryptographyECKey), - reason="Multiple crypto backends not available for backend compatibility tests" + reason="Multiple crypto backends not available for backend compatibility tests", ) -class TestBackendEcdsaCompatibility(object): - +class TestBackendEcdsaCompatibility: @pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey]) def test_signing_parity(self, BackendSign, BackendVerify): key_sign = BackendSign(private_key, ALGORITHMS.ES256) key_verify = BackendVerify(private_key, ALGORITHMS.ES256).public_key() - msg = b'test' + msg = b"test" sig = key_sign.sign(msg) # valid signature assert key_verify.verify(msg, sig) # invalid signature - assert not key_verify.verify(msg, b'n' * 64) + assert not key_verify.verify(msg, b"n" * 64) @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) def test_public_key_to_pem(self, BackendFrom, BackendTo): key = BackendFrom(private_key, ALGORITHMS.ES256) key2 = BackendTo(private_key, ALGORITHMS.ES256) assert key.public_key().to_pem().strip() == key2.public_key().to_pem().strip() @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) def test_private_key_to_pem(self, BackendFrom, BackendTo): key = BackendFrom(private_key, ALGORITHMS.ES256) key2 = BackendTo(private_key, ALGORITHMS.ES256) assert key.to_pem().strip() == key2.to_pem().strip() @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) def test_public_key_load_cycle(self, BackendFrom, BackendTo): key = BackendFrom(private_key, ALGORITHMS.ES256) pubkey = key.public_key() pub_pem_source = pubkey.to_pem().strip() pub_target = BackendTo(pub_pem_source, ALGORITHMS.ES256) assert pub_pem_source == pub_target.to_pem().strip() @pytest.mark.parametrize("BackendFrom", [ECDSAECKey, CryptographyECKey]) @pytest.mark.parametrize("BackendTo", [ECDSAECKey, CryptographyECKey]) def test_private_key_load_cycle(self, BackendFrom, BackendTo): key = BackendFrom(private_key, ALGORITHMS.ES256) pem_source = key.to_pem().strip() target = BackendTo(pem_source, ALGORITHMS.ES256) assert pem_source == target.to_pem().strip() diff --git a/tests/algorithms/test_HMAC.py b/tests/algorithms/test_HMAC.py index e84c2c0..2b0859e 100644 --- a/tests/algorithms/test_HMAC.py +++ b/tests/algorithms/test_HMAC.py @@ -1,45 +1,48 @@ +import json +import pytest + +from jose.backends.native import HMACKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError -from jose.jwk import HMACKey - -import pytest class TestHMACAlgorithm: - def test_non_string_key(self): with pytest.raises(JOSEError): HMACKey(object(), ALGORITHMS.HS256) def test_RSA_key(self): key = "-----BEGIN PUBLIC KEY-----" with pytest.raises(JOSEError): HMACKey(key, ALGORITHMS.HS256) key = "-----BEGIN RSA PUBLIC KEY-----" with pytest.raises(JOSEError): HMACKey(key, ALGORITHMS.HS256) key = "-----BEGIN CERTIFICATE-----" with pytest.raises(JOSEError): HMACKey(key, ALGORITHMS.HS256) key = "ssh-rsa" with pytest.raises(JOSEError): HMACKey(key, ALGORITHMS.HS256) def test_to_dict(self): - passphrase = 'The quick brown fox jumps over the lazy dog' - encoded = b'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw' + passphrase = "The quick brown fox jumps over the lazy dog" + encoded = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw" key = HMACKey(passphrase, ALGORITHMS.HS256) as_dict = key.to_dict() - assert 'alg' in as_dict - assert as_dict['alg'] == ALGORITHMS.HS256 + assert "alg" in as_dict + assert as_dict["alg"] == ALGORITHMS.HS256 + + assert "kty" in as_dict + assert as_dict["kty"] == "oct" - assert 'kty' in as_dict - assert as_dict['kty'] == 'oct' + assert "k" in as_dict + assert as_dict["k"] == encoded - assert 'k' in as_dict - assert as_dict['k'] == encoded + # as_dict should be serializable to JSON + json.dumps(as_dict) diff --git a/tests/algorithms/test_HMAC_compat.py b/tests/algorithms/test_HMAC_compat.py new file mode 100644 index 0000000..f2fb899 --- /dev/null +++ b/tests/algorithms/test_HMAC_compat.py @@ -0,0 +1,46 @@ +import pytest + +try: + from jose.backends.cryptography_backend import CryptographyHMACKey +except ImportError: + CryptographyHMACKey = None + +from jose.backends.native import HMACKey +from jose.constants import ALGORITHMS + +CRYPTO_BACKENDS = ( + pytest.param(CryptographyHMACKey, id="pyca/cryptography"), + pytest.param(HMACKey, id="native"), +) + +SUPPORTED_ALGORITHMS = ALGORITHMS.HMAC + + +@pytest.mark.backend_compatibility +@pytest.mark.skipif( + CryptographyHMACKey is None, reason="Multiple crypto backends not available for backend compatibility tests" +) +class TestBackendAesCompatibility: + @pytest.mark.parametrize("backend_sign", CRYPTO_BACKENDS) + @pytest.mark.parametrize("backend_verify", CRYPTO_BACKENDS) + @pytest.mark.parametrize("algorithm", SUPPORTED_ALGORITHMS) + def test_encryption_parity(self, backend_sign, backend_verify, algorithm): + if "128" in algorithm: + key = b"8slRzzty6dKMaFCP" + elif "192" in algorithm: + key = b"8slRzzty6dKMaFCP8slRzzty" + else: + key = b"8slRzzty6dKMaFCP8slRzzty6dKMaFCP" + + key_sign = backend_sign(key, algorithm) + key_verify = backend_verify(key, algorithm) + + message = b"test" + + digest = key_sign.sign(message) + + assert key_verify.verify(message, digest) + + assert not key_verify.verify(b"not the message", digest) + + assert not key_verify.verify(digest, b"not the digest") diff --git a/tests/algorithms/test_RSA.py b/tests/algorithms/test_RSA.py index 97aeb20..c1a7ff0 100644 --- a/tests/algorithms/test_RSA.py +++ b/tests/algorithms/test_RSA.py @@ -1,385 +1,378 @@ import base64 -import sys +import json try: - from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey from jose.backends import rsa_backend + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey except ImportError: PurePythonRSAKey = rsa_backend = None try: from Crypto.PublicKey import RSA as PyCryptoRSA - from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey except ImportError: - PyCryptoRSA = PyCryptoRSAKey = None + PyCryptoRSA = None try: from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa as pyca_rsa + from jose.backends.cryptography_backend import CryptographyRSAKey except ImportError: default_backend = pyca_rsa = CryptographyRSAKey = None +import pytest + from jose.backends import RSAKey from jose.constants import ALGORITHMS from jose.exceptions import JOSEError, JWKError -import pytest - -# Deal with integer compatibilities between Python 2 and 3. -# Using `from builtins import int` is not supported on AppEngine. -if sys.version_info > (3,): - long = int - private_key_4096_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M //dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XHGz69Q7yHK6DbxYO4w4q2 sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5aynQpwaVv2U++CL6EvGt 8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v82LneTdsqA25Wi2Ld/c 0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp8uppGF02Nz2v3ld8g CnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQv61VogqRKDU2r3/V ezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7iIfXlkJAZH XhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72Sust9Hnd9B7V35 YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6NsRk3jNdVMCAwEA AQKCAgEArx+0JXigDHtFZr4pYEPjwMgCBJ2dr8+L8PptB/4g+LoK9MKqR7M4aTO+ PoILPXPyWvZq/meeDakyZLrcdc8ad1ArKF7baDBpeGEbkRA9JfV5HjNq/ea4gyvD MCGou8ZPSQCnkRmr8LFQbJDgnM5Za5AYrwEv2aEh67IrTHq53W83rMioIumCNiG+ 7TQ7egEGiYsQ745GLrECLZhKKRTgt/T+k1cSk1LLJawme5XgJUw+3D9GddJEepvY oL+wZ/gnO2ADyPnPdQ7oc2NPcFMXpmIQf29+/g7FflatfQhkIv+eC6bB51DhdMi1 zyp2hOhzKg6jn74ixVX+Hts2/cMiAPu0NaWmU9n8g7HmXWc4+uSO/fssGjI3DLYK d5xnhrq4a3ZO5oJLeMO9U71+Ykctg23PTHwNAGrsPYdjGcBnJEdtbXa31agI5PAG 6rgGUY3iSoWqHLgBTxrX04TWVvLQi8wbxh7BEF0yasOeZKxdE2IWYg75zGsjluyH lOnpRa5lSf6KZ6thh9eczFHYtS4DvYBcZ9hZW/g87ie28SkBFxxl0brYt9uKNYJv uajVG8kT80AC7Wzg2q7Wmnoww3JNJUbNths5dqKyUSlMFMIB/vOePFHLrA6qDfAn sQHgUb9WHhUrYsH20XKpqR2OjmWU05bV4pSMW/JwG37o+px1yKECggEBANnwx0d7 ksEMvJjeN5plDy3eMLifBI+6SL/o5TXDoFM6rJxF+0UP70uouYJq2dI+DCSA6c/E sn7WAOirY177adKcBV8biwAtmKHnFnCs/kwAZq8lMvQPtNPJ/vq2n40kO48h8fxb eGcmyAqFPZ4YKSxrPA4cdbHIuFSt9WyaUcVFmzdTFHVlRP70EXdmXHt84byWNB4C Heq8zmrNxPNAi65nEkUks7iBQMtuvyV2+aXjDOTBMCd66IhIh2iZq1O7kXUwgh1O H9hCa7oriHyAdgkKdKCWocmbPPENOETgjraA9wRIXwOYTDb1X5hMvi1mCHo8xjMj u4szD03xJVi7WrsCggEBANTEblCkxEyhJqaMZF3U3df2Yr/ZtHqsrTr4lwB/MOKk zmuSrROxheEkKIsxbiV+AxTvtPR1FQrlqbhTJRwy+pw4KPJ7P4fq2R/YBqvXSNBC amTt6l2XdXqnAk3A++cOEZ2lU9ubfgdeN2Ih8rgdn1LWeOSjCWfExmkoU61/Xe6x AMeXKQSlHKSnX9voxuE2xINHeU6ZAKy1kGmrJtEiWnI8b8C4s8fTyDtXJ1Lasys0 iHO2Tz2jUhf4IJwb87Lk7Ize2MrI+oPzVDXlmkbjkB4tYyoiRTj8rk8pwBW/HVv0 02pjOLTa4kz1kQ3lsZ/3As4zfNi7mWEhadmEsAIfYkkCggEBANO39r/Yqj5kUyrm ZXnVxyM2AHq58EJ4I4hbhZ/vRWbVTy4ZRfpXeo4zgNPTXXvCzyT/HyS53vUcjJF7 PfPdpXX2H7m/Fg+8O9S8m64mQHwwv5BSQOecAnzkdJG2q9T/Z+Sqg1w2uAbtQ9QE kFFvA0ClhBfpSeTGK1wICq3QVLOh5SGf0fYhxR8wl284v4svTFRaTpMAV3Pcq2JS N4xgHdH1S2hkOTt6RSnbklGg/PFMWxA3JMKVwiPy4aiZ8DhNtQb1ctFpPcJm9CRN ejAI06IAyD/hVZZ2+oLp5snypHFjY5SDgdoKL7AMOyvHEdEkmAO32ot/oQefOLTt GOzURVUCggEBALSx5iYi6HtT2SlUzeBKaeWBYDgiwf31LGGKwWMwoem5oX0GYmr5 NwQP20brQeohbKiZMwrxbF+G0G60Xi3mtaN6pnvYZAogTymWI4RJH5OO9CCnVYUK nkD+GRzDqqt97UP/Joq5MX08bLiwsBvhPG/zqVQzikdQfFjOYNJV+wY92LWpELLb Lso/Q0/WDyExjA8Z4lH36vTCddTn/91Y2Ytu/FGmCzjICaMrzz+0cLlesgvjZsSo MY4dskQiEQN7G9I/Z8pAiVEKlBf52N4fYUPfs/oShMty/O5KPNG7L0nrUKlnfr9J rStC2l/9FK8P7pgEbiD6obY11FlhMMF8udECggEBAIKhvOFtipD1jqDOpjOoR9sK /lRR5bVVWQfamMDN1AwmjJbVHS8hhtYUM/4sh2p12P6RgoO8fODf1vEcWFh3xxNZ E1pPCPaICD9i5U+NRvPz2vC900HcraLRrUFaRzwhqOOknYJSBrGzW+Cx3YSeaOCg nKyI8B5gw4C0G0iL1dSsz2bR1O4GNOVfT3R6joZEXATFo/Kc2L0YAvApBNUYvY0k bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" private_key_2048_pkcs1 = b"""-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEAom6GcUPchmHxBuV3zJ60EPC7y30WiiVxn1WXSPHmfqaj0q2U xS03YugkYmX9lB/EQ6Z5bOY9VuL1oMudL6Dkb9aYYEBZHVgejV7vtYuYT19QMesn AsmGq8etie7XyWHzfWTxljbF53yvxXJMixcFzebAov9pUiV9Hmy3hYVLw3J1NXVg gPZpUT2oF+qAayhPsOi2b0CrIE3FvioDx7IiRXKFpV/1gah3NRSKxCrsxV6V+UGO +trP1ViWiu4oXB5j25kZmkgI0lXG60p58DUUeCOnEemvurltf9T9IEs7LGBEzUYm itGSY4ZOY3MabPypRfFRRotZEDyZjshq4xfXAwIDAQABAoIBAFclRzoTd4gdmevi RwDgEKmaDpchGGurpScgC5eWONywWOpaOJwFI1cMRyEHqSHEXU8STMkxSa2I/NF1 DHMWNhkOoBfbzjPhKBse2Sqkp2XGNEdj6z0ik/8rlR6QpvMjezhGZRr7bfhBPCiJ pylkg7exWp7Yu0/YTyV4nImlNz23GvrYHFtzDzTtn9gW4fe46wI08s4PqH/TyBh8 QkwkTwOKTk6n/xz2hND/shUOGjaoS0o6y4+8v3O1JYUWa7YZaIFofvF/dHR0yieg 2gQjc0c6+VeBm8dEbn3he+KnIBwQbWsiCuWL6Jq4XPtMbqutfovIYf9lRB+3q2PI VSh3mwECgYEAzhOhG+usoxjJGk2wVJH5wnHL0zyH8gWF4SnnxwwdBOF4kdLB2eva SJsi8rJQMT0TC4wZ6TsD2fJXGazIyM6OnD+52AViiUsLVS5MR7qEMNitdkWEtDx9 Xve50NF9XkTrn6+cgqvfJ9ezE4cOaiD3Eov1u/HbHRx3K2Qf9IzvGoMCgYEAycgk yOSYR0A3xKcfCQyOU0THEZWBBzd6zBAQ6VQNbcWHh1E8Ve0td6cvCxfydW1pIzXE 7b7J/BgMoC9oSQPfqQJQF2O0XESrdNgXjscfFpVgPfzbFQNgf7d0DSq4b/A5n5QR HVMmWzVQoRQUwqTNeVxs0NpY6W6Meqv3i/KJqYECgYA/KyMyhM55fCqA5pmLgueV Y/5/tMlTNcAxIgBLMnpeuaKUyI7ldveFVBClZmVQgpEo8/wpUw6+Kxvp4d32N+Ld IGeeQSBQR3Gk3blCL3k/49tgKrUf7n7bsoIB8YVFdUjovRLzty2DcAoTjU2s2IgD 5mUgBGYPCV+6LEnjU6QjcwKBgGg+0FJBVzKoSKd+N5hzNixqwfWhqXFTBkvamQIS fIWToTsVivhRekXwx2sRyh9EkSaxprW09aEZw5wWIehm6evk1//dcNaiW3oYEcOf t73xGjGsKnsmrXoOCxSqV3LtRrfcxSLDTHOejbNKLpeIkOb8CvOzem/OvyC5K0DP 4rMBAoGBAJStRo5xQ2F9cyZW8vLd4eR3FHXxF/7Moxr6AyV3RLUjMhkwB8ZcFLUQ dXI4NN9leDeIpNaGU6ozr+At3f50GtCWxdUppy9FDh5qDamBV8K4/+uNqFPiKFQ9 uwNcJ8daMgVZ0QBrD3CBcSZQrfC484BlV6spJ3C16qDVSQPt7sAI -----END RSA PRIVATE KEY-----""" PRIVATE_KEYS = ( pytest.param(private_key_2048_pkcs1, id="RSA_2048_PKCS1"), - pytest.param(private_key_4096_pkcs1, id="RSA_4096_PKCS1") + pytest.param(private_key_4096_pkcs1, id="RSA_4096_PKCS1"), ) LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM = b"""-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFADCCCSsCAQACggIBALUin0niNH7pESF9/AB5 SgdWD6V/D1GDdiXb3zPl4zZi31NTO+DdFZncyF/ebJ3kBjvZAtsTCBPgCJbedmH/ yoAWYIAJUvWNFcfA7Ei9h7zo4LgHV4q972C7wMsh4p/5lIrCTqnHBSgoRyo55NLl 8v9/Pta9e/3GE6e+m6yqVNSOJDNtDP/3W7ywVo388sPXobn6++GlcK/tMSX7AVa9 qGkBcMP1xxs+vUO8hyug28WDuMOKtrCH3AuKU/F0zx6OCWdjO99xGvGux8bWUuet /5oYUWS1OWsp0KcGlb9lPvgi+hLxrfE5TWTpHkb/MM/kbfAe9I86EaVSt+q0fqRy pV4TBk+tfb/Ni53k3bKgNuVoti3f3NJ4rrpduAOvmmo9rvUlm8QPS5lbRZ7bzW0W h1xNUi6Sz6CKfLqaRhdNjc9r95XfIAp001n6vwUPNEMvHtHKEUQARAma4yDMxxIO jJaEQ8uJ2tKUUL+tVaIKkSg1Nq9/1XsxT0A293ImLGY1ga9x6TTpFI067y5hcjhP UOUf6kBpnOgWLX5Oa5+4iH15ZCQGR14QcvhJQbogTPmEpBTO3R/drEiKGdOVeDD9 PV3Kace5HcLCcu9krrLfR53fQe1d+WJ1Relu/dZVR53p4QiTs4kZpB+MSy2z5Gkk 9irNyBx+7VZbTOjbEZN4zXVTAgMBAAECggIBAK8ftCV4oAx7RWa+KWBD48DIAgSd na/Pi/D6bQf+IPi6CvTCqkezOGkzvj6CCz1z8lr2av5nng2pMmS63HXPGndQKyhe 22gwaXhhG5EQPSX1eR4zav3muIMrwzAhqLvGT0kAp5EZq/CxUGyQ4JzOWWuQGK8B L9mhIeuyK0x6ud1vN6zIqCLpgjYhvu00O3oBBomLEO+ORi6xAi2YSikU4Lf0/pNX EpNSyyWsJnuV4CVMPtw/RnXSRHqb2KC/sGf4JztgA8j5z3UO6HNjT3BTF6ZiEH9v fv4OxX5WrX0IZCL/ngumwedQ4XTItc8qdoTocyoOo5++IsVV/h7bNv3DIgD7tDWl plPZ/IOx5l1nOPrkjv37LBoyNwy2CnecZ4a6uGt2TuaCS3jDvVO9fmJHLYNtz0x8 DQBq7D2HYxnAZyRHbW12t9WoCOTwBuq4BlGN4kqFqhy4AU8a19OE1lby0IvMG8Ye wRBdMmrDnmSsXRNiFmIO+cxrI5bsh5Tp6UWuZUn+imerYYfXnMxR2LUuA72AXGfY WVv4PO4ntvEpARccZdG62LfbijWCb7mo1RvJE/NAAu1s4Nqu1pp6MMNyTSVGzbYb OXaislEpTBTCAf7znjxRy6wOqg3wJ7EB4FG/Vh4VK2LB9tFyqakdjo5llNOW1eKU jFvycBt+6PqcdcihAoIBAQDZ8MdHe5LBDLyY3jeaZQ8t3jC4nwSPuki/6OU1w6BT OqycRftFD+9LqLmCatnSPgwkgOnPxLJ+1gDoq2Ne+2nSnAVfG4sALZih5xZwrP5M AGavJTL0D7TTyf76tp+NJDuPIfH8W3hnJsgKhT2eGCksazwOHHWxyLhUrfVsmlHF RZs3UxR1ZUT+9BF3Zlx7fOG8ljQeAh3qvM5qzcTzQIuuZxJFJLO4gUDLbr8ldvml 4wzkwTAneuiISIdomatTu5F1MIIdTh/YQmu6K4h8gHYJCnSglqHJmzzxDThE4I62 gPcESF8DmEw29V+YTL4tZgh6PMYzI7uLMw9N8SVYu1q7AoIBAQDUxG5QpMRMoSam jGRd1N3X9mK/2bR6rK06+JcAfzDipM5rkq0TsYXhJCiLMW4lfgMU77T0dRUK5am4 UyUcMvqcOCjyez+H6tkf2Aar10jQQmpk7epdl3V6pwJNwPvnDhGdpVPbm34HXjdi IfK4HZ9S1njkowlnxMZpKFOtf13usQDHlykEpRykp1/b6MbhNsSDR3lOmQCstZBp qybRIlpyPG/AuLPH08g7VydS2rMrNIhztk89o1IX+CCcG/Oy5OyM3tjKyPqD81Q1 5ZpG45AeLWMqIkU4/K5PKcAVvx1b9NNqYzi02uJM9ZEN5bGf9wLOM3zYu5lhIWnZ hLACH2JJAoIBAQDTt/a/2Ko+ZFMq5mV51ccjNgB6ufBCeCOIW4Wf70Vm1U8uGUX6 V3qOM4DT0117ws8k/x8kud71HIyRez3z3aV19h+5vxYPvDvUvJuuJkB8ML+QUkDn nAJ85HSRtqvU/2fkqoNcNrgG7UPUBJBRbwNApYQX6UnkxitcCAqt0FSzoeUhn9H2 IcUfMJdvOL+LL0xUWk6TAFdz3KtiUjeMYB3R9UtoZDk7ekUp25JRoPzxTFsQNyTC lcIj8uGomfA4TbUG9XLRaT3CZvQkTXowCNOiAMg/4VWWdvqC6ebJ8qRxY2OUg4Ha Ci+wDDsrxxHRJJgDt9qLf6EHnzi07Rjs1EVVAoIBAQC0seYmIuh7U9kpVM3gSmnl gWA4IsH99SxhisFjMKHpuaF9BmJq+TcED9tG60HqIWyomTMK8WxfhtButF4t5rWj eqZ72GQKIE8pliOESR+TjvQgp1WFCp5A/hkcw6qrfe1D/yaKuTF9PGy4sLAb4Txv 86lUM4pHUHxYzmDSVfsGPdi1qRCy2y7KP0NP1g8hMYwPGeJR9+r0wnXU5//dWNmL bvxRpgs4yAmjK88/tHC5XrIL42bEqDGOHbJEIhEDexvSP2fKQIlRCpQX+djeH2FD 37P6EoTLcvzuSjzRuy9J61CpZ36/Sa0rQtpf/RSvD+6YBG4g+qG2NdRZYTDBfLnR AoIBAQCCobzhbYqQ9Y6gzqYzqEfbCv5UUeW1VVkH2pjAzdQMJoyW1R0vIYbWFDP+ LIdqddj+kYKDvHzg39bxHFhYd8cTWRNaTwj2iAg/YuVPjUbz89rwvdNB3K2i0a1B Wkc8IajjpJ2CUgaxs1vgsd2EnmjgoJysiPAeYMOAtBtIi9XUrM9m0dTuBjTlX090 eo6GRFwExaPynNi9GALwKQTVGL2NJG4yfyX0zudOtErFn7X+IsN464Up/UcE02Ha v5BKxhVrwxiZ9jIroTHtqJzX1cyBkZnVMR8ItbpZLKQJ/35mO39IWabJA8HB8mZm ymbpPjVPxSfCAHJr5Pcu5tuZ0knP -----END PRIVATE KEY----- """ +RSA_KW_ALGOS = ( + pytest.param(ALGORITHMS.RSA_OAEP, id="RSA_OAEP"), + pytest.param(ALGORITHMS.RSA_OAEP_256, id="RSA_OAEP_256"), +) + def _legacy_invalid_private_key_pkcs8_der(): legacy_key = LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM.strip() - legacy_key = legacy_key[legacy_key.index(b"\n"):legacy_key.rindex(b"\n")] + legacy_key = legacy_key[legacy_key.index(b"\n") : legacy_key.rindex(b"\n")] return base64.b64decode(legacy_key) def _actually_invalid_private_key_pkcs8_der(): legacy_key = _legacy_invalid_private_key_pkcs8_der() - invalid_key = legacy_key[:len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER)] + invalid_key = legacy_key[: len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER)] invalid_key += b"\x00" - invalid_key += legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + invalid_key += legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER) :] return invalid_key def _actually_invalid_private_key_pkcs8_pem(): invalid_key = b"-----BEGIN PRIVATE KEY-----\n" invalid_key += base64.b64encode(_actually_invalid_private_key_pkcs8_der()) invalid_key += b"\n-----END PRIVATE KEY-----\n" return invalid_key @pytest.mark.skipif(PurePythonRSAKey is None, reason="python-rsa backend not available") -class TestPurePythonRsa(object): - +class TestPurePythonRsa: def test_python_rsa_legacy_pem_read(self): key = PurePythonRSAKey(LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM, ALGORITHMS.RS256) new_pem = key.to_pem(pem_format="PKCS8") assert new_pem != LEGACY_INVALID_PRIVATE_KEY_PKCS8_PEM def test_python_rsa_legacy_pem_invalid(self): with pytest.raises(JWKError) as excinfo: PurePythonRSAKey(_actually_invalid_private_key_pkcs8_pem(), ALGORITHMS.RS256) excinfo.match("Invalid private key encoding") def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1(self): legacy_key = _legacy_invalid_private_key_pkcs8_der() - legacy_pkcs1 = legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER):] + legacy_pkcs1 = legacy_key[len(rsa_backend.LEGACY_INVALID_PKCS8_RSA_HEADER) :] assert rsa_backend._legacy_private_key_pkcs8_to_pkcs1(legacy_key) == legacy_pkcs1 def test_python_rsa_legacy_private_key_pkcs8_to_pkcs1_invalid(self): invalid_key = _actually_invalid_private_key_pkcs8_der() with pytest.raises(ValueError) as excinfo: rsa_backend._legacy_private_key_pkcs8_to_pkcs1(invalid_key) excinfo.match("Invalid private key encoding") -@pytest.mark.pycrypto -@pytest.mark.pycryptodome -@pytest.mark.skipif(None in (PyCryptoRSA, PyCryptoRSAKey), reason="Pycrypto/dome backend not available") -def test_pycrypto_RSA_key_instance(): - key = PyCryptoRSA.construct((long( - 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), - long(65537))) - PyCryptoRSAKey(key, ALGORITHMS.RS256) - - -# TODO: Unclear why this test was marked as only for pycrypto -@pytest.mark.pycrypto -@pytest.mark.pycryptodome -@pytest.mark.parametrize("private_key", PRIVATE_KEYS) -@pytest.mark.skipif(None in (PyCryptoRSA, PyCryptoRSAKey), reason="Pycrypto/dome backend not available") -def test_pycrypto_unencoded_cleartext(private_key): - key = PyCryptoRSAKey(private_key, ALGORITHMS.RS256) - msg = b'test' - signature = key.sign(msg) - public_key = key.public_key() - - assert bool(public_key.verify(msg, signature)) - assert not bool(public_key.verify(msg, 1)) - - @pytest.mark.cryptography @pytest.mark.skipif( - None in (default_backend, pyca_rsa, CryptographyRSAKey), - reason="Cryptography backend not available" + None in (default_backend, pyca_rsa, CryptographyRSAKey), reason="Cryptography backend not available" ) def test_cryptography_RSA_key_instance(): key = pyca_rsa.RSAPublicNumbers( - long(65537), - long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819), + int(65537), + int( + 26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819 + ), ).public_key(default_backend()) pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256) assert pubkey.is_public() pem = pubkey.to_pem() - assert pem.startswith(b'-----BEGIN PUBLIC KEY-----') + assert pem.startswith(b"-----BEGIN PUBLIC KEY-----") +@pytest.mark.cryptography +@pytest.mark.parametrize("private_key_pem", PRIVATE_KEYS) +@pytest.mark.parametrize("algorithm", RSA_KW_ALGOS) +@pytest.mark.skipif( + None in (default_backend, pyca_rsa, CryptographyRSAKey), reason="Cryptography backend not available" +) +def test_cryptography_wrap_key_unencoded_cleartext(private_key_pem, algorithm): + private_key = CryptographyRSAKey(private_key_pem, algorithm) + key = b"test" + public_key = private_key.public_key() + wrapped = public_key.wrap_key(key) + unwrapped = private_key.unwrap_key(wrapped) + assert unwrapped == key + + +@pytest.mark.skipif(RSAKey is None, reason="RSA is not available") class TestRSAAlgorithm: def test_RSA_key(self): assert not RSAKey(private_key_4096_pkcs1, ALGORITHMS.RS256).is_public() def test_string_secret(self): - key = 'secret' + key = "secret" with pytest.raises(JOSEError): RSAKey(key, ALGORITHMS.RS256) def test_object(self): key = object() with pytest.raises(JOSEError): RSAKey(key, ALGORITHMS.RS256) - def test_bad_cert(self,): - key = '-----BEGIN CERTIFICATE-----' + def test_bad_cert( + self, + ): + key = "-----BEGIN CERTIFICATE-----" with pytest.raises(JOSEError): RSAKey(key, ALGORITHMS.RS256) def test_invalid_algorithm(self): with pytest.raises(JWKError): RSAKey(private_key_4096_pkcs1, ALGORITHMS.ES256) with pytest.raises(JWKError): - RSAKey({'kty': 'bla'}, ALGORITHMS.RS256) + RSAKey({"kty": "bla"}, ALGORITHMS.RS256) def test_RSA_jwk(self): key = { "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", "e": "AQAB", } assert RSAKey(key, ALGORITHMS.RS256).is_public() key = { "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", "d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ", "p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nRaO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmGpeNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8bUq0k", "q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc", "dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX 59ehik", "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8", - "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4" + "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4", } assert not RSAKey(key, ALGORITHMS.RS256).is_public() - del key['p'] + del key["p"] # Some but not all extra parameters are present with pytest.raises(JWKError): RSAKey(key, ALGORITHMS.RS256) - del key['q'] - del key['dp'] - del key['dq'] - del key['qi'] + del key["q"] + del key["dp"] + del key["dq"] + del key["qi"] # None of the extra parameters are present, but 'key' is still private. assert not RSAKey(key, ALGORITHMS.RS256).is_public() @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_get_public_key(self, private_key): key = RSAKey(private_key, ALGORITHMS.RS256) public_key = key.public_key() public_key2 = public_key.public_key() assert public_key.is_public() assert public_key2.is_public() assert public_key == public_key2 @pytest.mark.parametrize("pkey", PRIVATE_KEYS) def test_to_pem(self, pkey): key = RSAKey(pkey, ALGORITHMS.RS256) - assert key.to_pem(pem_format='PKCS1').strip() == pkey.strip() + assert key.to_pem(pem_format="PKCS1").strip() == pkey.strip() - pkcs8 = key.to_pem(pem_format='PKCS8').strip() + pkcs8 = key.to_pem(pem_format="PKCS8").strip() assert pkcs8 != pkey.strip() newkey = RSAKey(pkcs8, ALGORITHMS.RS256) - assert newkey.to_pem(pem_format='PKCS1').strip() == pkey.strip() + assert newkey.to_pem(pem_format="PKCS1").strip() == pkey.strip() def assert_parameters(self, as_dict, private): assert isinstance(as_dict, dict) # Public parameters should always be there. - assert 'n' in as_dict - assert 'e' in as_dict + assert "n" in as_dict + assert "e" in as_dict if private: # Private parameters as well - assert 'd' in as_dict - assert 'p' in as_dict - assert 'q' in as_dict - assert 'dp' in as_dict - assert 'dq' in as_dict - assert 'qi' in as_dict + assert "d" in as_dict + assert "p" in as_dict + assert "q" in as_dict + assert "dp" in as_dict + assert "dq" in as_dict + assert "qi" in as_dict else: # Private parameters should be absent - assert 'd' not in as_dict - assert 'p' not in as_dict - assert 'q' not in as_dict - assert 'dp' not in as_dict - assert 'dq' not in as_dict - assert 'qi' not in as_dict + assert "d" not in as_dict + assert "p" not in as_dict + assert "q" not in as_dict + assert "dp" not in as_dict + assert "dq" not in as_dict + assert "qi" not in as_dict + + # as_dict should be serializable to JSON + json.dumps(as_dict) def assert_roundtrip(self, key): - assert RSAKey( - key.to_dict(), - ALGORITHMS.RS256 - ).to_dict() == key.to_dict() + assert RSAKey(key.to_dict(), ALGORITHMS.RS256).to_dict() == key.to_dict() @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_to_dict(self, private_key): key = RSAKey(private_key, ALGORITHMS.RS256) self.assert_parameters(key.to_dict(), private=True) self.assert_parameters(key.public_key().to_dict(), private=False) self.assert_roundtrip(key) self.assert_roundtrip(key.public_key()) diff --git a/tests/algorithms/test_RSA_compat.py b/tests/algorithms/test_RSA_compat.py index 0da03d5..02eb3f6 100644 --- a/tests/algorithms/test_RSA_compat.py +++ b/tests/algorithms/test_RSA_compat.py @@ -1,102 +1,124 @@ import pytest try: - from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey from jose.backends.cryptography_backend import CryptographyRSAKey - from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey + from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey except ImportError: - PurePythonRSAKey = CryptographyRSAKey = PyCryptoRSAKey = None + PurePythonRSAKey = CryptographyRSAKey = None from jose.constants import ALGORITHMS +from jose.exceptions import JWEError from .test_RSA import PRIVATE_KEYS CRYPTO_BACKENDS = ( pytest.param(PurePythonRSAKey, id="python_rsa"), pytest.param(CryptographyRSAKey, id="pyca/cryptography"), - pytest.param(PyCryptoRSAKey, id="pycrypto/dome") ) ENCODINGS = ("PKCS1", "PKCS8") @pytest.mark.backend_compatibility @pytest.mark.skipif( - None in (PurePythonRSAKey, CryptographyRSAKey, PyCryptoRSAKey), - reason="Multiple crypto backends not available for backend compatibility tests" + None in (PurePythonRSAKey, CryptographyRSAKey), + reason="Multiple crypto backends not available for backend compatibility tests", ) -class TestBackendRsaCompatibility(object): - +class TestBackendRsaCompatibility: @pytest.mark.parametrize("BackendSign", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendVerify", CRYPTO_BACKENDS) @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_signing_parity(self, BackendSign, BackendVerify, private_key): key_sign = BackendSign(private_key, ALGORITHMS.RS256) key_verify = BackendVerify(private_key, ALGORITHMS.RS256).public_key() - msg = b'test' + msg = b"test" sig = key_sign.sign(msg) # valid signature assert key_verify.verify(msg, sig) # invalid signature - assert not key_verify.verify(msg, b'n' * 64) + assert not key_verify.verify(msg, b"n" * 64) @pytest.mark.parametrize("encoding", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_public_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): key = BackendFrom(private_key, ALGORITHMS.RS256) key2 = BackendTo(private_key, ALGORITHMS.RS256) key1_pem = key.public_key().to_pem(pem_format=encoding).strip() key2_pem = key2.public_key().to_pem(pem_format=encoding).strip() assert key1_pem == key2_pem @pytest.mark.parametrize("encoding", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_private_key_to_pem(self, BackendFrom, BackendTo, encoding, private_key): key = BackendFrom(private_key, ALGORITHMS.RS256) key2 = BackendTo(private_key, ALGORITHMS.RS256) key1_pem = key.to_pem(pem_format=encoding).strip() key2_pem = key2.to_pem(pem_format=encoding).strip() import base64 - a = base64.b64decode(key1_pem[key1_pem.index(b"\n"):key1_pem.rindex(b"\n")]) - b = base64.b64decode(key2_pem[key2_pem.index(b"\n"):key2_pem.rindex(b"\n")]) + + a = base64.b64decode(key1_pem[key1_pem.index(b"\n") : key1_pem.rindex(b"\n")]) + b = base64.b64decode(key2_pem[key2_pem.index(b"\n") : key2_pem.rindex(b"\n")]) assert a == b assert key1_pem == key2_pem @pytest.mark.parametrize("encoding_save", ENCODINGS) @pytest.mark.parametrize("encoding_load", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_public_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): key = BackendFrom(private_key, ALGORITHMS.RS256) pem_pub_reference = key.public_key().to_pem(pem_format=encoding_save).strip() pem_pub_load = key.public_key().to_pem(pem_format=encoding_load).strip() pubkey_2 = BackendTo(pem_pub_load, ALGORITHMS.RS256) assert pem_pub_reference == pubkey_2.to_pem(encoding_save).strip() @pytest.mark.parametrize("encoding_save", ENCODINGS) @pytest.mark.parametrize("encoding_load", ENCODINGS) @pytest.mark.parametrize("BackendFrom", CRYPTO_BACKENDS) @pytest.mark.parametrize("BackendTo", CRYPTO_BACKENDS) @pytest.mark.parametrize("private_key", PRIVATE_KEYS) def test_private_key_load_cycle(self, BackendFrom, BackendTo, encoding_save, encoding_load, private_key): key = BackendFrom(private_key, ALGORITHMS.RS256) pem_reference = key.to_pem(pem_format=encoding_save).strip() pem_load = key.to_pem(pem_format=encoding_load).strip() key_2 = BackendTo(pem_load, ALGORITHMS.RS256) assert pem_reference == key_2.to_pem(encoding_save).strip() + + @pytest.mark.parametrize("backend_wrap", CRYPTO_BACKENDS) + @pytest.mark.parametrize("backend_unwrap", CRYPTO_BACKENDS) + @pytest.mark.parametrize("algorithm", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.RSA_KW)) + @pytest.mark.parametrize("private_key", PRIVATE_KEYS) + def test_key_wrap_parity(self, backend_wrap, backend_unwrap, private_key, algorithm): + if algorithm in (ALGORITHMS.RSA_OAEP, ALGORITHMS.RSA_OAEP_256) and PurePythonRSAKey in ( + backend_wrap, + backend_unwrap, + ): + pytest.skip("Pure RSA does not support OAEP") + key_wrap = backend_wrap(private_key, algorithm).public_key() + key_unwrap = backend_unwrap(private_key, algorithm) + + unwrapped_key = b"test" + wrapped_key = key_wrap.wrap_key(unwrapped_key) + + # verify unwrap to original key + actual = key_unwrap.unwrap_key(wrapped_key) + assert actual == unwrapped_key + + with pytest.raises(JWEError): + key_unwrap.unwrap_key(b"n" * 64) diff --git a/tests/algorithms/test_base.py b/tests/algorithms/test_base.py index 6f7fcb7..85c8a80 100644 --- a/tests/algorithms/test_base.py +++ b/tests/algorithms/test_base.py @@ -1,19 +1,36 @@ -from jose.jwk import Key - import pytest +from jose.jwk import Key + @pytest.fixture def alg(): return Key("key", "ALG") class TestBaseAlgorithm: - def test_sign_is_interface(self, alg): with pytest.raises(NotImplementedError): - alg.sign('msg') + alg.sign("msg") def test_verify_is_interface(self, alg): with pytest.raises(NotImplementedError): - alg.verify('msg', 'sig') + alg.verify("msg", "sig") + + def test_encrypt_is_interface(self, alg): + with pytest.raises(NotImplementedError): + alg.encrypt( + "plain text", + ) + + def test_decrypt_is_interface(self, alg): + with pytest.raises(NotImplementedError): + alg.decrypt("plain text", iv="iv") + + def test_wrap_key_is_interface(self, alg): + with pytest.raises(NotImplementedError): + alg.wrap_key("plain text") + + def test_unwrap_key_is_interface(self, alg): + with pytest.raises(NotImplementedError): + alg.unwrap_key("plain text") diff --git a/tests/rfc/__init__.pyc b/tests/rfc/__init__.pyc deleted file mode 100644 index be005fd..0000000 Binary files a/tests/rfc/__init__.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/__init__.cpython-34.pyc b/tests/rfc/__pycache__/__init__.cpython-34.pyc deleted file mode 100644 index f300c5e..0000000 Binary files a/tests/rfc/__pycache__/__init__.cpython-34.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/__init__.cpython-36.pyc b/tests/rfc/__pycache__/__init__.cpython-36.pyc deleted file mode 100644 index 128b029..0000000 Binary files a/tests/rfc/__pycache__/__init__.cpython-36.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc7520.cpython-27-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc7520.cpython-27-PYTEST.pyc deleted file mode 100644 index 53e9da6..0000000 Binary files a/tests/rfc/__pycache__/test_rfc7520.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc7520.cpython-34-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc7520.cpython-34-PYTEST.pyc deleted file mode 100644 index 11160d2..0000000 Binary files a/tests/rfc/__pycache__/test_rfc7520.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc7520.cpython-36-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc7520.cpython-36-PYTEST.pyc deleted file mode 100644 index cf45a4f..0000000 Binary files a/tests/rfc/__pycache__/test_rfc7520.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc8037.cpython-27-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc8037.cpython-27-PYTEST.pyc deleted file mode 100644 index fdf37ba..0000000 Binary files a/tests/rfc/__pycache__/test_rfc8037.cpython-27-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc8037.cpython-34-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc8037.cpython-34-PYTEST.pyc deleted file mode 100644 index c9ba795..0000000 Binary files a/tests/rfc/__pycache__/test_rfc8037.cpython-34-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/__pycache__/test_rfc8037.cpython-36-PYTEST.pyc b/tests/rfc/__pycache__/test_rfc8037.cpython-36-PYTEST.pyc deleted file mode 100644 index 88cf2dc..0000000 Binary files a/tests/rfc/__pycache__/test_rfc8037.cpython-36-PYTEST.pyc and /dev/null differ diff --git a/tests/rfc/test_rfc7520.py b/tests/rfc/test_rfc7520.py index 568e692..08adaed 100644 --- a/tests/rfc/test_rfc7520.py +++ b/tests/rfc/test_rfc7520.py @@ -1,6783 +1,5809 @@ - # Disable flake8 reporting # flake8: noqa +import pytest + +from jose.backends import RSAKey from jose.jws import verify expected_payload = b"It\xe2\x80\x99s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there\xe2\x80\x99s no knowing where you might be swept off to." # [Docs] [txt|pdf] [draft-ietf-jose-c...] - # Internet Engineering Task Force (IETF) M. Miller # Request for Comments: 7520 Cisco Systems, Inc. # Category: Informational May 2015 # ISSN: 2070-1721 # Examples of Protecting Content Using # JSON Object Signing and Encryption (JOSE) # Abstract # This document contains a set of examples using JSON Object Signing # and Encryption (JOSE) technology to protect data. These examples # present a representative sampling of JSON Web Key (JWK) objects as # well as various JSON Web Signature (JWS) and JSON Web Encryption # (JWE) results given similar inputs. # Status of This Memo # This document is not an Internet Standards Track specification; it is # published for informational purposes. # This document is a product of the Internet Engineering Task Force # (IETF). It represents the consensus of the IETF community. It has # received public review and has been approved for publication by the # Internet Engineering Steering Group (IESG). Not all documents # approved by the IESG are a candidate for any level of Internet # Standard; see Section 2 of RFC 5741. # Information about the current status of this document, any errata, # and how to provide feedback on it may be obtained at # http://www.rfc-editor.org/info/rfc7520. # Copyright Notice # Copyright (c) 2015 IETF Trust and the persons identified as the # document authors. All rights reserved. # This document is subject to BCP 78 and the IETF Trust's Legal # Provisions Relating to IETF Documents # (http://trustee.ietf.org/license-info) in effect on the date of # publication of this document. Please review these documents # carefully, as they describe your rights and restrictions with respect # to this document. Code Components extracted from this document must # include Simplified BSD License text as described in Section 4.e of # the Trust Legal Provisions and are provided without warranty as # described in the Simplified BSD License. - - # Miller Informational [Page 1] # RFC 7520 JOSE Cookbook May 2015 # Table of Contents # 1. Introduction ....................................................5 # 1.1. Conventions Used in This Document ..........................5 # 2. Terminology .....................................................6 # 3. JSON Web Key Examples ...........................................6 # 3.1. EC Public Key ..............................................6 # 3.2. EC Private Key .............................................7 # 3.3. RSA Public Key .............................................8 # 3.4. RSA Private Key ............................................8 # 3.5. Symmetric Key (MAC Computation) ...........................10 # 3.6. Symmetric Key (Encryption) ................................11 # 4. JSON Web Signature Examples ....................................11 # 4.1. RSA v1.5 Signature ........................................12 # 4.1.1. Input Factors ......................................12 # 4.1.2. Signing Operation ..................................12 # 4.1.3. Output Results .....................................13 # 4.2. RSA-PSS Signature .........................................15 # 4.2.1. Input Factors ......................................15 # 4.2.2. Signing Operation ..................................16 # 4.2.3. Output Results .....................................17 # 4.3. ECDSA Signature ...........................................19 # 4.3.1. Input Factors ......................................19 # 4.3.2. Signing Operation ..................................19 # 4.3.3. Output Results .....................................20 # 4.4. HMAC-SHA2 Integrity Protection ............................21 # 4.4.1. Input Factors ......................................22 # 4.4.2. Signing Operation ..................................22 # 4.4.3. Output Results .....................................23 # 4.5. Signature with Detached Content ...........................24 # 4.5.1. Input Factors ......................................25 # 4.5.2. Signing Operation ..................................25 # 4.5.3. Output Results .....................................26 # 4.6. Protecting Specific Header Fields .........................27 # 4.6.1. Input Factors ......................................27 # 4.6.2. Signing Operation ..................................27 # 4.6.3. Output Results .....................................28 # 4.7. Protecting Content Only ...................................29 # 4.7.1. Input Factors ......................................30 # 4.7.2. Signing Operation ..................................30 # 4.7.3. Output Results .....................................31 # 4.8. Multiple Signatures .......................................32 # 4.8.1. Input Factors ......................................32 # 4.8.2. First Signing Operation ............................33 # 4.8.3. Second Signing Operation ...........................34 # 4.8.4. Third Signing Operation ............................36 # 4.8.5. Output Results .....................................37 # 5. JSON Web Encryption Examples ...................................39 - # Miller Informational [Page 2] # RFC 7520 JOSE Cookbook May 2015 # 5.1. Key Encryption Using RSA v1.5 and AES-HMAC-SHA2 ...........39 # 5.1.1. Input Factors ......................................39 # 5.1.2. Generated Factors ..................................41 # 5.1.3. Encrypting the Key .................................41 # 5.1.4. Encrypting the Content .............................42 # 5.1.5. Output Results .....................................43 # 5.2. Key Encryption Using RSA-OAEP with AES-GCM ................45 # 5.2.1. Input Factors ......................................46 # 5.2.2. Generated Factors ..................................47 # 5.2.3. Encrypting the Key .................................48 # 5.2.4. Encrypting the Content .............................48 # 5.2.5. Output Results .....................................49 # 5.3. Key Wrap Using PBES2-AES-KeyWrap with AES-CBC-HMAC-SHA2 ...52 # 5.3.1. Input Factors ......................................53 # 5.3.2. Generated Factors ..................................54 # 5.3.3. Encrypting the Key .................................54 # 5.3.4. Encrypting the Content .............................55 # 5.3.5. Output Results .....................................56 # 5.4. Key Agreement with Key Wrapping Using ECDH-ES and # AES-KeyWrap with AES-GCM ..................................59 # 5.4.1. Input Factors ......................................59 # 5.4.2. Generated Factors ..................................60 # 5.4.3. Encrypting the Key .................................60 # 5.4.4. Encrypting the Content .............................61 # 5.4.5. Output Results .....................................63 # 5.5. Key Agreement Using ECDH-ES with AES-CBC-HMAC-SHA2 ........65 # 5.5.1. Input Factors ......................................66 # 5.5.2. Generated Factors ..................................66 # 5.5.3. Key Agreement ......................................67 # 5.5.4. Encrypting the Content .............................67 # 5.5.5. Output Results .....................................68 # 5.6. Direct Encryption Using AES-GCM ...........................70 # 5.6.1. Input Factors ......................................70 # 5.6.2. Generated Factors ..................................70 # 5.6.3. Encrypting the Content .............................71 # 5.6.4. Output Results .....................................72 # 5.7. Key Wrap Using AES-GCM KeyWrap with AES-CBC-HMAC-SHA2 .....73 # 5.7.1. Input Factors ......................................73 # 5.7.2. Generated Factors ..................................74 # 5.7.3. Encrypting the Key .................................74 # 5.7.4. Encrypting the Content .............................75 # 5.7.5. Output Results .....................................77 # 5.8. Key Wrap Using AES-KeyWrap with AES-GCM ...................79 # 5.8.1. Input Factors ......................................79 # 5.8.2. Generated Factors ..................................80 # 5.8.3. Encrypting the Key .................................80 # 5.8.4. Encrypting the Content .............................80 # 5.8.5. Output Results .....................................82 - # Miller Informational [Page 3] # RFC 7520 JOSE Cookbook May 2015 # 5.9. Compressed Content ........................................84 # 5.9.1. Input Factors ......................................84 # 5.9.2. Generated Factors ..................................84 # 5.9.3. Encrypting the Key .................................85 # 5.9.4. Encrypting the Content .............................85 # 5.9.5. Output Results .....................................86 # 5.10. Including Additional Authenticated Data ..................88 # 5.10.1. Input Factors .....................................88 # 5.10.2. Generated Factors .................................89 # 5.10.3. Encrypting the Key ................................90 # 5.10.4. Encrypting the Content ............................90 # 5.10.5. Output Results ....................................91 # 5.11. Protecting Specific Header Fields ........................93 # 5.11.1. Input Factors .....................................93 # 5.11.2. Generated Factors .................................94 # 5.11.3. Encrypting the Key ................................94 # 5.11.4. Encrypting the Content ............................94 # 5.11.5. Output Results ....................................95 # 5.12. Protecting Content Only ..................................97 # 5.12.1. Input Factors .....................................97 # 5.12.2. Generated Factors .................................98 # 5.12.3. Encrypting the Key ................................98 # 5.12.4. Encrypting the Content ............................98 # 5.12.5. Output Results ....................................99 # 5.13. Encrypting to Multiple Recipients .......................101 # 5.13.1. Input Factors ....................................101 # 5.13.2. Generated Factors ................................101 # 5.13.3. Encrypting the Key to the First Recipient ........102 # 5.13.4. Encrypting the Key to the Second Recipient .......103 # 5.13.5. Encrypting the Key to the Third Recipient ........105 # 5.13.6. Encrypting the Content ...........................106 # 5.13.7. Output Results ...................................108 # 6. Nesting Signatures and Encryption .............................110 # 6.1. Signing Input Factors ....................................110 # 6.2. Signing Operation ........................................112 # 6.3. Signing Output ...........................................112 # 6.4. Encryption Input Factors .................................113 # 6.5. Encryption Generated Factors .............................113 # 6.6. Encrypting the Key .......................................114 # 6.7. Encrypting the Content ...................................114 # 6.8. Encryption Output ........................................115 # 7. Security Considerations .......................................119 # 8. References ....................................................119 # 8.1. Normative References .....................................119 # 8.2. Informative References ...................................120 # Acknowledgements .................................................120 # Author's Address .................................................120 - - # Miller Informational [Page 4] # RFC 7520 JOSE Cookbook May 2015 # 1. Introduction # The JSON Object Signing and Encryption (JOSE) technologies -- JSON # Web Signature [JWS], JSON Web Encryption [JWE], JSON Web Key [JWK], # and JSON Web Algorithms [JWA] -- can be used collectively to encrypt # and/or sign content using a variety of algorithms. While the full # set of permutations is extremely large, and might be daunting to # some, it is expected that most applications will only use a small set # of algorithms to meet their needs. # This document provides a number of examples of signing or encrypting # content using JOSE. While not exhaustive, it does compile a # representative sampling of JOSE features. As much as possible, the # same signature payload or encryption plaintext content is used to # illustrate differences in various signing and encryption results. # This document also provides a number of example JWK objects. These # examples illustrate the distinguishing properties of various key # types and emphasize important characteristics. Most of the JWK # examples are then used in the signature or encryption examples that # follow. # All of the examples contained herein are available in a machine- # readable format at . # 1.1. Conventions Used in This Document # This document separates data that are expected to be input to an # implementation of JOSE from data that are expected to be generated by # an implementation of JOSE. Each example, wherever possible, provides # enough information both to replicate the results of this document and # to validate the results by running its inverse operation (e.g., # signature results can be validated by performing the JWS verify). # However, some algorithms inherently use random data; therefore, # computations employing them cannot be exactly replicated. Such cases # are explicitly stated in the relevant sections. # All instances of binary octet strings are represented using base64url # [RFC4648] encoding. # Wherever possible and unless otherwise noted, the examples include # the JWS or JWE Compact Serialization, general JWS or JWE JSON # Serialization, and flattened JWS or JWE JSON Serialization. # All of the examples in this document have whitespace added to improve # formatting and readability. Except for JWE Plaintext or JWS Payload # content, whitespace is not part of the cryptographic operations nor # the exchange results. - # Miller Informational [Page 5] # RFC 7520 JOSE Cookbook May 2015 # Unless otherwise noted, the JWE Plaintext or JWS Payload content does # include " " (U+0020 SPACE) characters. Line breaks (U+000A LINE # FEED) replace some " " (U+0020 SPACE) characters to improve # readability but are not present in the JWE Plaintext or JWS Payload. # 2. Terminology # This document inherits terminology regarding JSON Web Signature (JWS) # technology from [JWS], terminology regarding JSON Web Encryption # (JWE) technology from [JWE], terminology regarding JSON Web Key (JWK) # technology from [JWK], and terminology regarding algorithms from # [JWA]. # 3. JSON Web Key Examples # The following sections demonstrate how to represent various JWK and # JWK Set objects. # 3.1. EC Public Key # This example illustrates an Elliptic Curve (EC) public key. This # example is the public key corresponding to the private key in # Figure 2. # Note that whitespace is added for readability as described in # Section 1.1. # { # "kty": "EC", # "kid": "bilbo.baggins@hobbiton.example", # "use": "sig", # "crv": "P-521", # "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9 # A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", # "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy # SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" # } ec_public_key = { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", - "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", } # Figure 1: Elliptic Curve P-521 Public Key # The field "kty" value of "EC" identifies this as an Elliptic Curve # key. The field "crv" identifies the curve, which is curve P-521 for # this example. The values of the fields "x" and "y" are the # base64url-encoded X and Y coordinates (respectively). - - - - - # Miller Informational [Page 6] # RFC 7520 JOSE Cookbook May 2015 # The values of the fields "x" and "y" decoded are the octets necessary # to represent each full coordinate to the order of the curve. For a # key over curve P-521, the values of the fields "x" and "y" are # exactly 66 octets in length when decoded, padded with leading zero # (0x00) octets to reach the expected length. # 3.2. EC Private Key # This example illustrates an Elliptic Curve private key. This example # is the private key corresponding to the public key in Figure 1. # Note that whitespace is added for readability as described in # Section 1.1. # { # "kty": "EC", # "kid": "bilbo.baggins@hobbiton.example", # "use": "sig", # "crv": "P-521", # "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9 # A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", # "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVy # SsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", # "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zb # KipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" # } # Figure 2: Elliptic Curve P-521 Private Key # The field "kty" value of "EC" identifies this as an Elliptic Curve # key. The field "crv" identifies the curve, which is curve P-521 # (also known as SECG curve secp521r1) for this example. The values of # the fields "x" and "y" are the base64url-encoded X and Y coordinates # (respectively). The field "d" value is the base64url-encoded private # key. # The values of the fields "d", "x", and "y" decoded are the octets # necessary to represent the private key or each full coordinate # (respectively) to the order of the curve. For a key over curve # P-521, the values of the "d", "x", and "y" fields are each exactly 66 # octets in length when decoded, padded with leading zero (0x00) octets # to reach the expected length. - - - - - - - # Miller Informational [Page 7] # RFC 7520 JOSE Cookbook May 2015 # 3.3. RSA Public Key # This example illustrates an RSA public key. This example is the # public key corresponding to the private key in Figure 4. # Note that whitespace is added for readability as described in # Section 1.1. # { # "kty": "RSA", # "kid": "bilbo.baggins@hobbiton.example", # "use": "sig", # "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT # -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV # wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj- # oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde # 3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC # LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g # HdrNP5zw", # "e": "AQAB" # } rsa_public_jwk = { "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" + "e": "AQAB", } # Figure 3: RSA 2048-Bit Public Key # The field "kty" value of "RSA" identifies this as an RSA key. The # fields "n" and "e" values are the modulus and (public) exponent # (respectively) using the minimum octets necessary. # For a 2048-bit key, the field "n" value is 256 octets in length when # decoded. # 3.4. RSA Private Key # This example illustrates an RSA private key. This example is the # private key corresponding to the public key in Figure 3. # Note that whitespace is added for readability as described in # Section 1.1. - - - - - - - - - - - # Miller Informational [Page 8] # RFC 7520 JOSE Cookbook May 2015 # { # "kty": "RSA", # "kid": "bilbo.baggins@hobbiton.example", # "use": "sig", # "n": "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT # -O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV # wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj- # oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde # 3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC # LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g # HdrNP5zw", # "e": "AQAB", # "d": "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e # iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld # Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b # MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU # 6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj # d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc # OpBrQzwQ", # "p": "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR # aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG # peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8 # bUq0k", # "q": "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT # 8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an # V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0 # s7pFc", # "dp": "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q # 1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn # -RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX # 59ehik", # "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr # AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK # bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK # T1cYF8", # "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N # ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh # jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP # z8aaI4" # } # Figure 4: RSA 2048-Bit Private Key - - - - - - - # Miller Informational [Page 9] # RFC 7520 JOSE Cookbook May 2015 # The field "kty" value of "RSA" identifies this as an RSA key. The # fields "n" and "e" values are the base64url-encoded modulus and # (public) exponent (respectively) using the minimum number of octets # necessary. The field "d" value is the base64url-encoded private # exponent using the minimum number of octets necessary. The fields # "p", "q", "dp", "dq", and "qi" are the base64url-encoded additional # private information using the minimum number of octets necessary. # For a 2048-bit key, the field "n" is 256 octets in length when # decoded, and the field "d" is not longer than 256 octets in length # when decoded. # 3.5. Symmetric Key (MAC Computation) # This example illustrates a symmetric key used for computing Message # Authentication Codes (MACs). # Note that whitespace is added for readability as described in # Section 1.1. # { # "kty": "oct", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", # "use": "sig", # "alg": "HS256", # "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" # } hmac_key = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", - "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", } # Figure 5: HMAC SHA-256 Symmetric Key # The field "kty" value of "oct" identifies this as a symmetric key. # The field "k" value is the symmetric key. # When used for the signing algorithm "HS256" (HMAC-SHA256), the field # "k" value is 32 octets (or more) in length when decoded, padded with # leading zero (0x00) octets to reach the minimum expected length. - - - - - - - - - - - - - # Miller Informational [Page 10] # RFC 7520 JOSE Cookbook May 2015 # 3.6. Symmetric Key (Encryption) # This example illustrates a symmetric key used for encryption. # Note that whitespace is added for readability as described in # Section 1.1. # { # "kty": "oct", # "kid": "1e571774-2e08-40da-8308-e8d68773842d", # "use": "enc", # "alg": "A256GCM", # "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8" # } # Figure 6: AES 256-Bit Symmetric Encryption Key # The field "kty" value of "oct" identifies this as a symmetric key. # The field "k" value is the symmetric key. # For the content encryption algorithm "A256GCM", the field "k" value # is exactly 32 octets in length when decoded, padded with leading zero # (0x00) octets to reach the expected length. # 4. JSON Web Signature Examples # The following sections demonstrate how to generate various JWS # objects. # All of the signature examples use the following payload content (an # abridged quote from "The Fellowship of the Ring" [LOTR-FELLOWSHIP]), # serialized as UTF-8. The payload is presented here as a series of # quoted strings that are concatenated to produce the JWS Payload. The # sequence "\xe2\x80\x99" is substituted for (U+2019 RIGHT SINGLE # QUOTATION MARK), and quotation marks (U+0022 QUOTATION MARK) are # added for readability but are not present in the JWS Payload. # "It\xe2\x80\x99s a dangerous business, Frodo, going out your " # "door. You step onto the road, and if you don't keep your feet, " # "there\xe2\x80\x99s no knowing where you might be swept off " # "to." # Figure 7: Payload Content Plaintext - - - - - - # Miller Informational [Page 11] # RFC 7520 JOSE Cookbook May 2015 # The payload -- with the sequence "\xe2\x80\x99" replaced with (U+2019 # RIGHT SINGLE QUOTATION MARK) and quotations marks (U+0022 QUOTATION # MARK) are removed -- is encoded as UTF-8 and then as base64url # [RFC4648]: # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 8: Payload Content, base64url-encoded # 4.1. RSA v1.5 Signature # This example illustrates signing content using the "RS256" (RSASSA- # PKCS1-v1_5 with SHA-256) algorithm. # Note that whitespace is added for readability as described in # Section 1.1. # 4.1.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o RSA private key; this example uses the key from Figure 4. # o "alg" parameter of "RS256". # 4.1.2. Signing Operation # The following is generated to complete the signing operation: # o JWS Protected Header; this example uses the header from Figure 9, # encoded using base64url [RFC4648] to produce Figure 10. # { # "alg": "RS256", # "kid": "bilbo.baggins@hobbiton.example" # } # Figure 9: JWS Protected Header JSON - - - - - # Miller Informational [Page 12] # RFC 7520 JOSE Cookbook May 2015 # eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # Figure 10: JWS Protected Header, base64url-encoded # The JWS Protected Header (Figure 10) and JWS Payload (Figure 8) are # combined as described in Section 5.1 of [JWS] to produce the JWS # Signing Input (Figure 11). # eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 11: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 11) produces the JWS Signature (Figure 12). # MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK # ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J # IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w # W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP # xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f # cIe8u9ipH84ogoree7vjbU5y18kDquDg # Figure 12: JWS Signature, base64url-encoded # 4.1.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 9) # o Payload content (Figure 8) # o Signature (Figure 12) - - - - - - - - - # Miller Informational [Page 13] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the JWS Compact Serialization: # eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # . # MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmK # ZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4J # IwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8w # W1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluP # xUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_f # cIe8u9ipH84ogoree7vjbU5y18kDquDg # Figure 13: JWS Compact Serialization + class TestFourOneThree: token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" + @pytest.mark.skipif(RSAKey is None, reason="RSA is not available") def test_signature(self): - payload = verify(self.token, rsa_public_jwk, 'RS256') + payload = verify(self.token, rsa_public_jwk, "RS256") assert payload == expected_payload + # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2 # dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHo # xnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII # 7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0Rnlt # uYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPo # cSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxU # Ahb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJush # Z41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" # } # ] # } # Figure 14: General JWS JSON Serialization - - - - - - # Miller Informational [Page 14] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "protected": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbn # NAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2 # e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84w # nB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_q # HRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9U # zpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0 # KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogore # e7vjbU5y18kDquDg" # } # Figure 15: Flattened JWS JSON Serialization # 4.2. RSA-PSS Signature # This example illustrates signing content using the "PS384" (RSASSA- # PSS with SHA-384) algorithm. # Note that RSASSA-PSS uses random data to generate the signature; it # might not be possible to exactly replicate the results in this # section. # Note that whitespace is added for readability as described in # Section 1.1. # 4.2.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o RSA private key; this example uses the key from Figure 4. # o "alg" parameter of "PS384". - - - - - - # Miller Informational [Page 15] # RFC 7520 JOSE Cookbook May 2015 # 4.2.2. Signing Operation # The following is generated to complete the signing operation: # o JWS Protected Header; this example uses the header from Figure 16, # encoded using base64url [RFC4648] to produce Figure 17. # { # "alg": "PS384", # "kid": "bilbo.baggins@hobbiton.example" # } # Figure 16: JWS Protected Header JSON # eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # Figure 17: JWS Protected Header, base64url-encoded # The JWS Protected Header (Figure 17) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 18). # eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 18: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 18) produces the JWS Signature (Figure 19). # cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I # pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU # vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX # e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT # 0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a # 6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw # Figure 19: JWS Signature, base64url-encoded - - - - - # Miller Informational [Page 16] # RFC 7520 JOSE Cookbook May 2015 # 4.2.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 17) # o Payload content (Figure 8) # o Signature (Figure 19) # The resulting JWS object using the JWS Compact Serialization: # eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # . # cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2I # pN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXU # vdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRX # e8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT # 0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a # 6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw # Figure 20: JWS Compact Serialization - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 17] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2 # dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy # 42miAh2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5 # dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz2 # 8zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vd # z0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0q # I0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uT # OcbH510a6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw" # } # ] # } # Figure 21: General JWS JSON Serialization # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "protected": "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbn # NAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42mi # Ah2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllV # o6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf # 8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vdz0TTrxUeT3lm8d9s # hnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aCN_2_jLAeQT # lqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6oD # 4ifKo8DYM-X72Eaw" # } # Figure 22: Flattened JWS JSON Serialization - - - - # Miller Informational [Page 18] # RFC 7520 JOSE Cookbook May 2015 # 4.3. ECDSA Signature # This example illustrates signing content using the "ES512" (Elliptic # Curve Digital Signature Algorithm (ECDSA) with curve P-521 and SHA- # 512) algorithm. # Note that ECDSA uses random data to generate the signature; it might # not be possible to exactly replicate the results in this section. # Note that whitespace is added for readability as described in # Section 1.1. # 4.3.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o EC private key on the curve P-521; this example uses the key from # Figure 2. # o "alg" parameter of "ES512". # 4.3.2. Signing Operation # The following is generated before beginning the signature process: # o JWS Protected Header; this example uses the header from Figure 23, # encoded using base64url [RFC4648] to produce Figure 24. # { # "alg": "ES512", # "kid": "bilbo.baggins@hobbiton.example" # } # Figure 23: JWS Protected Header JSON # eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # Figure 24: JWS Protected Header, base64url-encoded - - - - - - - # Miller Informational [Page 19] # RFC 7520 JOSE Cookbook May 2015 # The JWS Protected Header (Figure 24) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 25). # eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 25: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 25) produces the JWS Signature (Figure 26). # AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvb # u9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kv # AD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2 # Figure 26: JWS Signature, base64url-encoded # 4.3.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 24) # o Payload content (Figure 8) # o Signature (Figure 26) # The resulting JWS object using the JWS Compact Serialization: # eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZX # hhbXBsZSJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # . # AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvb # u9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kv # AD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2 # Figure 27: JWS Compact Serialization class TestFourThreeThree: token = "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2" def test_signature(self): - payload = verify(self.token, ec_public_key, 'ES512') + payload = verify(self.token, ec_public_key, "ES512") assert payload == expected_payload - # Miller Informational [Page 20] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2 # dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNl # aAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mt # PBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBp # HABlsbEPX6sFY8OcGDqoRuBomu9xQ2" # } # ] # } # Figure 28: General JWS JSON Serialization # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "protected": "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbn # NAaG9iYml0b24uZXhhbXBsZSJ9", # "signature": "AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP # 2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sD # DyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sF # Y8OcGDqoRuBomu9xQ2" # } # Figure 29: Flattened JWS JSON Serialization # 4.4. HMAC-SHA2 Integrity Protection # This example illustrates integrity protecting content using the # "HS256" (HMAC-SHA-256) algorithm. # Note that whitespace is added for readability as described in # Section 1.1. - - # Miller Informational [Page 21] # RFC 7520 JOSE Cookbook May 2015 # 4.4.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o HMAC symmetric key; this example uses the key from Figure 5. # o "alg" parameter of "HS256". # 4.4.2. Signing Operation # The following is generated before completing the signing operation: # o JWS Protected Header; this example uses the header from Figure 30, # encoded using base64url [RFC4648] to produce Figure 31. # { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # } # Figure 30: JWS Protected Header JSON # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # Figure 31: JWS Protected Header, base64url-encoded # The JWS Protected Header (Figure 31) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 32). # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 32: JWS Signing Input - - - - - - # Miller Informational [Page 22] # RFC 7520 JOSE Cookbook May 2015 # Performing the signature operation over the JWS Signing Input # (Figure 32) produces the JWS Signature (Figure 33). # s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 # Figure 33: JWS Signature, base64url-encoded # 4.4.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 31) # o Payload content (Figure 8) # o Signature (Figure 33) # The resulting JWS object using the JWS Compact Serialization: # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # . # s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 # Figure 34: JWS Compact Serialization class TestFourFourThree: token = "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" def test_signature(self): - payload = verify(self.token, hmac_key, 'HS256') + payload = verify(self.token, hmac_key, "HS256") assert payload == expected_payload - - - - - - - - - - - - - - - - # Miller Informational [Page 23] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT # RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p # 0" # } # ] # } # Figure 35: General JWS JSON Serialization # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW # ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" # } # Figure 36: Flattened JWS JSON Serialization # 4.5. Signature with Detached Content # This example illustrates a signature with detached content. This # example is identical to other examples in Section 4, except the # resulting JWS objects do not include the JWS Payload field. Instead, # the application is expected to locate it elsewhere. For example, the # signature might be in a metadata section, with the payload being the # content. # Note that whitespace is added for readability as described in # Section 1.1. - - - # Miller Informational [Page 24] # RFC 7520 JOSE Cookbook May 2015 # 4.5.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o Signing key; this example uses the AES symmetric key from # Figure 5. # o Signing algorithm; this example uses "HS256". # 4.5.2. Signing Operation # The following is generated before completing the signing operation: # o JWS Protected Header; this example uses the header from Figure 37, # encoded using base64url [RFC4648] to produce Figure 38. # { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # } # Figure 37: JWS Protected Header JSON # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # Figure 38: JWS Protected Header, base64url-encoded # The JWS Protected Header (Figure 38) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 39). # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 39: JWS Signing Input - - - - - # Miller Informational [Page 25] # RFC 7520 JOSE Cookbook May 2015 # Performing the signature operation over the JWS Signing Input # (Figure 39) produces the JWS Signature (Figure 40). # s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 # Figure 40: JWS Signature, base64url-encoded # 4.5.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 38) # o Signature (Figure 40) # The resulting JWS object using the JWS Compact Serialization: # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # . # . # s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 # Figure 41: General JWS JSON Serialization # The resulting JWS object using the general JWS JSON Serialization: # { # "signatures": [ # { # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT # RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p # 0" # } # ] # } # Figure 42: General JWS JSON Serialization - - - - - - - - - - # Miller Informational [Page 26] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the flattened JWS JSON Serialization: # { # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW # ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" # } # Figure 43: Flattened JWS JSON Serialization # 4.6. Protecting Specific Header Fields # This example illustrates a signature where only certain Header # Parameters are protected. Since this example contains both # unprotected and protected Header Parameters, only the general JWS # JSON Serialization and flattened JWS JSON Serialization are possible. # Note that whitespace is added for readability as described in # Section 1.1. # 4.6.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o Signing key; this example uses the AES symmetric key from # Figure 5. # o Signing algorithm; this example uses "HS256". # 4.6.2. Signing Operation # The following are generated before completing the signing operation: # o JWS Protected Header; this example uses the header from Figure 44, # encoded using base64url [RFC4648] to produce Figure 45. # o JWS Unprotected Header; this example uses the header from # Figure 46. # { # "alg": "HS256" # } # Figure 44: JWS Protected Header JSON - - # Miller Informational [Page 27] # RFC 7520 JOSE Cookbook May 2015 # eyJhbGciOiJIUzI1NiJ9 # Figure 45: JWS Protected Header, base64url-encoded # { # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # } # Figure 46: JWS Unprotected Header JSON # The JWS Protected Header (Figure 45) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 47). # eyJhbGciOiJIUzI1NiJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 47: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 47) produces the JWS Signature (Figure 48). # bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr20 # Figure 48: JWS Signature, base64url-encoded # 4.6.3. Output Results # The following compose the resulting JWS object: # o JWS Protected Header (Figure 45) # o JWS Unprotected Header (Figure 46) # o Payload content (Figure 8) # o Signature (Figure 48) # The JWS Compact Serialization is not presented because it does not # support this use case. - - - - - # Miller Informational [Page 28] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJIUzI1NiJ9", # "header": { # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # }, # "signature": "bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr2 # 0" # } # ] # } # Figure 49: General JWS JSON Serialization # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "protected": "eyJhbGciOiJIUzI1NiJ9", # "header": { # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # }, # "signature": "bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr20" # } # Figure 50: Flattened JWS JSON Serialization # 4.7. Protecting Content Only # This example illustrates a signature where none of the Header # Parameters are protected. Since this example contains only # unprotected Header Parameters, only the general JWS JSON # Serialization and flattened JWS JSON Serialization are possible. # Note that whitespace is added for readability as described in # Section 1.1. - # Miller Informational [Page 29] # RFC 7520 JOSE Cookbook May 2015 # 4.7.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o Signing key; this example uses the AES symmetric key from # Figure 5. # o Signing algorithm; this example uses "HS256". # 4.7.2. Signing Operation # The following is generated before completing the signing operation: # o JWS Unprotected Header; this example uses the header from # Figure 51. # { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # } # Figure 51: JWS Unprotected Header JSON # The empty string (as there is no JWS Protected Header) and JWS # Payload (Figure 8) are combined as described in [JWS] to produce the # JWS Signing Input (Figure 52). # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 52: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 52) produces the JWS Signature (Figure 53). # xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZuk # Figure 53: JWS Signature, base64url-encoded - - - - - # Miller Informational [Page 30] # RFC 7520 JOSE Cookbook May 2015 # 4.7.3. Output Results # The following compose the resulting JWS object: # o JWS Unprotected Header (Figure 51) # o Payload content (Figure 8) # o Signature (Figure 53) # The JWS Compact Serialization is not presented because it does not # support this use case. # The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "header": { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # }, # "signature": "xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZu # k" # } # ] # } # Figure 54: General JWS JSON Serialization - - - - - - - - - - - - - - - # Miller Informational [Page 31] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the flattened JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "header": { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # }, # "signature": "xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZuk" # } # Figure 55: Flattened JWS JSON Serialization # 4.8. Multiple Signatures # This example illustrates multiple signatures applied to the same # payload. Since this example contains more than one signature, only # the JSON General Serialization is possible. # Note that whitespace is added for readability as described in # Section 1.1. # 4.8.1. Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the content from Figure 7, # encoded using base64url [RFC4648] to produce Figure 8. # o Signing keys; this example uses the following: # * RSA private key from Figure 4 for the first signature # * EC private key from Figure 2 for the second signature # * AES symmetric key from Figure 5 for the third signature # o Signing algorithms; this example uses the following: # * "RS256" for the first signature # * "ES512" for the second signature # * "HS256" for the third signature - # Miller Informational [Page 32] # RFC 7520 JOSE Cookbook May 2015 # 4.8.2. First Signing Operation # The following are generated before completing the first signing # operation: # o JWS Protected Header; this example uses the header from Figure 56, # encoded using base64url [RFC4648] to produce Figure 57. # o JWS Unprotected Header; this example uses the header from # Figure 58. # { # "alg": "RS256" # } # Figure 56: Signature #1 JWS Protected Header JSON # eyJhbGciOiJSUzI1NiJ9 # Figure 57: Signature #1 JWS Protected Header, base64url-encoded # { # "kid": "bilbo.baggins@hobbiton.example" # } # Figure 58: Signature #1 JWS Unprotected Header JSON # The JWS Protected Header (Figure 57) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 59). # eyJhbGciOiJSUzI1NiJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 59: JWS Signing Input - - - - - - - - - - # Miller Informational [Page 33] # RFC 7520 JOSE Cookbook May 2015 # Performing the signature operation over the JWS Signing Input # (Figure 59) produces the JWS Signature (Figure 60). # MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5NvyG53uoimic1tcMdSg-qpt # rzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFcryNFiHkSw129EghGpwkpxaTn_THJTC # glNbADko1MZBCdwzJxwqZc-1RlpO2HibUYyXSwO97BSe0_evZKdjvvKSgsIqjy # tKSeAMbhMBdMma622_BG5t4sdbuCHtFjp9iJmkio47AIwqkZV1aIZsv33uPUqB # BCXbYoQJwt7mxPftHmNlGoOSMxR_3thmXTCm4US-xiNOyhbm8afKK64jU6_TPt # QHiJeQJxz9G3Tx-083B745_AfYOnlC9w # Figure 60: JWS Signature #1, base64url-encoded # The following is the assembled first signature serialized as JSON: # { # "protected": "eyJhbGciOiJSUzI1NiJ9", # "header": { # "kid": "bilbo.baggins@hobbiton.example" # }, # "signature": "MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5NvyG53u # oimic1tcMdSg-qptrzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFcryNFiHkS # w129EghGpwkpxaTn_THJTCglNbADko1MZBCdwzJxwqZc-1RlpO2HibUY # yXSwO97BSe0_evZKdjvvKSgsIqjytKSeAMbhMBdMma622_BG5t4sdbuC # HtFjp9iJmkio47AIwqkZV1aIZsv33uPUqBBCXbYoQJwt7mxPftHmNlGo # OSMxR_3thmXTCm4US-xiNOyhbm8afKK64jU6_TPtQHiJeQJxz9G3Tx-0 # 83B745_AfYOnlC9w" # } # Figure 61: Signature #1 JSON # 4.8.3. Second Signing Operation # The following is generated before completing the second signing # operation: # o JWS Unprotected Header; this example uses the header from # Figure 62. # { # "alg": "ES512", # "kid": "bilbo.baggins@hobbiton.example" # } # Figure 62: Signature #2 JWS Unprotected Header JSON - - - - - # Miller Informational [Page 34] # RFC 7520 JOSE Cookbook May 2015 # The empty string (as there is no JWS Protected Header) and JWS # Payload (Figure 8) are combined as described in [JWS] to produce the # JWS Signing Input (Figure 63). # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 63: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 63) produces the JWS Signature (Figure 64). # ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhcdCoFZFFjfISu0Cdkn9Yb # dlmi54ho0x924DUz8sK7ZXkhc7AFM8ObLfTvNCrqcI3Jkl2U5IX3utNhODH6v7 # xgy1Qahsn0fyb4zSAkje8bAWz4vIfj5pCMYxxm4fgV3q7ZYhm5eD # Figure 64: JWS Signature #2, base64url-encoded # The following is the assembled second signature serialized as JSON: # { # "header": { # "alg": "ES512", # "kid": "bilbo.baggins@hobbiton.example" # }, # "signature": "ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhcdCoF # ZFFjfISu0Cdkn9Ybdlmi54ho0x924DUz8sK7ZXkhc7AFM8ObLfTvNCrq # cI3Jkl2U5IX3utNhODH6v7xgy1Qahsn0fyb4zSAkje8bAWz4vIfj5pCM # Yxxm4fgV3q7ZYhm5eD" # } # Figure 65: Signature #2 JSON - - - - - - - - - - - - - - # Miller Informational [Page 35] # RFC 7520 JOSE Cookbook May 2015 # 4.8.4. Third Signing Operation # The following is generated before completing the third signing # operation: # o JWS Protected Header; this example uses the header from Figure 66, # encoded using base64url [RFC4648] to produce Figure 67. # { # "alg": "HS256", # "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" # } # Figure 66: Signature #3 JWS Protected Header JSON # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # Figure 67: Signature #3 JWS Protected Header, base64url-encoded # The JWS Protected Header (Figure 67) and JWS Payload (Figure 8) are # combined as described in [JWS] to produce the JWS Signing Input # (Figure 68). # eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW # VlZjMxNGJjNzAzNyJ9 # . # SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IH # lvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBk # b24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcm # UgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4 # Figure 68: JWS Signing Input # Performing the signature operation over the JWS Signing Input # (Figure 68) produces the JWS Signature (Figure 69). # s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0 # Figure 69: JWS Signature #3, base64url-encoded - - - - - - - - - # Miller Informational [Page 36] # RFC 7520 JOSE Cookbook May 2015 # The following is the assembled third signature serialized as JSON: # { # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOW # ItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" # } # Figure 70: Signature #3 JSON # 4.8.5. Output Results # The following compose the resulting JWS object: # o Payload content (Figure 8) # o Signature #1 JSON (Figure 61) # o Signature #2 JSON (Figure 65) # o Signature #3 JSON (Figure 70) # The JWS Compact Serialization is not presented because it does not # support this use case; the flattened JWS JSON Serialization is not # presented because there is more than one signature. +# Miller Informational [Page 37] +# RFC 7520 JOSE Cookbook May 2015 - - - - - - - - - - - - - - - - - - - - - -# Miller Informational [Page 37] - -# RFC 7520 JOSE Cookbook May 2015 - - -# The resulting JWS object using the general JWS JSON Serialization: +# The resulting JWS object using the general JWS JSON Serialization: # { # "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg # Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h # ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi # gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m # ZiB0by4", # "signatures": [ # { # "protected": "eyJhbGciOiJSUzI1NiJ9", # "header": { # "kid": "bilbo.baggins@hobbiton.example" # }, # "signature": "MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5Nvy # G53uoimic1tcMdSg-qptrzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFc # ryNFiHkSw129EghGpwkpxaTn_THJTCglNbADko1MZBCdwzJxwqZc # -1RlpO2HibUYyXSwO97BSe0_evZKdjvvKSgsIqjytKSeAMbhMBdM # ma622_BG5t4sdbuCHtFjp9iJmkio47AIwqkZV1aIZsv33uPUqBBC # XbYoQJwt7mxPftHmNlGoOSMxR_3thmXTCm4US-xiNOyhbm8afKK6 # 4jU6_TPtQHiJeQJxz9G3Tx-083B745_AfYOnlC9w" # }, # { # "header": { # "alg": "ES512", # "kid": "bilbo.baggins@hobbiton.example" # }, # "signature": "ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhc # dCoFZFFjfISu0Cdkn9Ybdlmi54ho0x924DUz8sK7ZXkhc7AFM8Ob # LfTvNCrqcI3Jkl2U5IX3utNhODH6v7xgy1Qahsn0fyb4zSAkje8b # AWz4vIfj5pCMYxxm4fgV3q7ZYhm5eD" # }, # { # "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LT # RkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", # "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p # 0" # } # ] # } # Figure 71: General JWS JSON Serialization - - - - - - - # Miller Informational [Page 38] # RFC 7520 JOSE Cookbook May 2015 # 5. JSON Web Encryption Examples # The following sections demonstrate how to generate various JWE # objects. # All of the encryption examples (unless otherwise noted) use the # following Plaintext content (an abridged quote from "The Fellowship # of the Ring" [LOTR-FELLOWSHIP]), serialized as UTF-8. The Plaintext # is presented here as a series of quoted strings that are concatenated # to produce the JWE Plaintext. The sequence "\xe2\x80\x93" is # substituted for (U+2013 EN DASH), and quotation marks (U+0022 # QUOTATION MARK) are added for readability but are not present in the # JWE Plaintext. # "You can trust us to stick with you through thick and " # "thin\xe2\x80\x93to the bitter end. And you can trust us to " # "keep any secret of yours\xe2\x80\x93closer than you keep it " # "yourself. But you cannot trust us to let you face trouble " # "alone, and go off without a word. We are your friends, Frodo." # Figure 72: Plaintext Content # 5.1. Key Encryption Using RSA v1.5 and AES-HMAC-SHA2 # This example illustrates encrypting content using the "RSA1_5" # (RSAES-PKCS1-v1_5) key encryption algorithm and the "A128CBC-HS256" # (AES-128-CBC-HMAC-SHA-256) content encryption algorithm. # Note that RSAES-PKCS1-v1_5 uses random data to generate the # ciphertext; it might not be possible to exactly replicate the results # in this section. # Note that only the RSA public key is necessary to perform the # encryption. However, the example includes the RSA private key to # allow readers to validate the output. # Note that whitespace is added for readability as described in # Section 1.1. # 5.1.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o RSA public key; this example uses the key from Figure 73. - - - # Miller Informational [Page 39] # RFC 7520 JOSE Cookbook May 2015 # o "alg" parameter of "RSA1_5". # o "enc" parameter of "A128CBC-HS256". # { # "kty": "RSA", # "kid": "frodo.baggins@hobbiton.example", # "use": "enc", # "n": "maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegT # HVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx # 6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5U # NwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4c # R5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oy # pBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYA # VotGlvMQ", # "e": "AQAB", # "d": "Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wy # bQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO # 5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6 # Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP # 1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PN # miuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2v # pzj85bQQ", # "p": "2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaE # oekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH # 7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ # 2VFmU", # "q": "te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_V # F099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb # 9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8 # d6Et0", # "dp": "UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTH # QmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JV # RDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsf # lo0rYU", # "dq": "iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9Mb # pFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87A # CfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14 # TkXlHE", # "qi": "kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZ # lXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7 # Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx # 2bQ_mM" # } # Figure 73: RSA 2048-Bit Key, in JWK Format - - - # Miller Informational [Page 40] # RFC 7520 JOSE Cookbook May 2015 # (NOTE: While the key includes the private parameters, only the public # parameters "e" and "n" are necessary for the encryption operation.) # 5.1.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 74. # o Initialization Vector; this example uses the Initialization Vector # from Figure 75. # 3qyTVhIWt5juqZUCpfRqpvauwB956MEJL2Rt-8qXKSo # Figure 74: Content Encryption Key, base64url-encoded # bbd5sTkYwhAIqfHsx8DayA # Figure 75: Initialization Vector, base64url-encoded # 5.1.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 74) with # the RSA key (Figure 73) results in the following Encrypted Key: # laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF # vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G # Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG # TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl # zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh # MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw # Figure 76: Encrypted Key, base64url-encoded - - - - - - - - - - - - - - - # Miller Informational [Page 41] # RFC 7520 JOSE Cookbook May 2015 # 5.1.4. Encrypting the Content # The following is generated before encrypting the Plaintext: # o JWE Protected Header; this example uses the header from Figure 77, # encoded using base64url [RFC4648] to produce Figure 78. # { # "alg": "RSA1_5", # "kid": "frodo.baggins@hobbiton.example", # "enc": "A128CBC-HS256" # } # Figure 77: JWE Protected Header JSON # eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm # V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0 # Figure 78: JWE Protected Header, base64url-encoded # Performing the content encryption operation on the Plaintext # (Figure 72) using the following: # o CEK (Figure 74); # o Initialization Vector (Figure 75); and # o JWE Protected Header (Figure 77) as authenticated data # produces the following: # o Ciphertext from Figure 79. # o Authentication Tag from Figure 80. # 0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r # aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O # WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV # yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0 # zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2 # O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW # i7lzA6BP430m # Figure 79: Ciphertext, base64url-encoded # kvKuFBXHe5mQr4lqgobAUg # Figure 80: Authentication Tag, base64url-encoded - # Miller Informational [Page 42] # RFC 7520 JOSE Cookbook May 2015 # 5.1.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 78) # o Encrypted Key (Figure 76) # o Initialization Vector (Figure 75) # o Ciphertext (Figure 79) # o Authentication Tag (Figure 80) # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLm # V4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0 # . # laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePF # vG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2G # Xfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcG # TSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8Vl # zNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOh # MBs9M8XL223Fg47xlGsMXdfuY-4jaqVw # . # bbd5sTkYwhAIqfHsx8DayA # . # 0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_r # aa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8O # WzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZV # yeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0 # zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2 # O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VW # i7lzA6BP430m # . # kvKuFBXHe5mQr4lqgobAUg # Figure 81: JWE Compact Serialization - - - - - - - - - - # Miller Informational [Page 43] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzf # TihJBuuzxg0V7yk1WClnQePFvG2K-pvSlWc9BRIazDrn50RcRai_ # _3TDON395H3c62tIouJJ4XaRvYHFjZTZ2GXfz8YAImcc91Tfk0WX # C2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcGTSLUeeCt # 36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8 # VlzNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx # 1BpyIfgvfjOhMBs9M8XL223Fg47xlGsMXdfuY-4jaqVw" # } # ], # "protected": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW # 5zQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In # 0", # "iv": "bbd5sTkYwhAIqfHsx8DayA", # "ciphertext": "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62 # JhJvGZ4_FNVSiGc_raa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wn # I3Jvo0mkpEEnlDmZvDu_k8OWzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc # 2VbVbK4dQKPdNTjPPEmRqcaGeTWZVyeSUvf5k59yJZxRuSvWFf6KrNtm # RdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0zT5CbL5Qlw3sRc7u_hg0y # KVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2O6_7uInMGhFeX4c # tHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VWi7lzA6BP4 # 30m", # "tag": "kvKuFBXHe5mQr4lqgobAUg" # } # Figure 82: General JWE JSON Serialization - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 44] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW # 5zQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In # 0", # "encrypted_key": "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJ # Buuzxg0V7yk1WClnQePFvG2K-pvSlWc9BRIazDrn50RcRai__3TDON39 # 5H3c62tIouJJ4XaRvYHFjZTZ2GXfz8YAImcc91Tfk0WXC2F5Xbb71ClQ # 1DDH151tlpH77f2ff7xiSxh9oSewYrcGTSLUeeCt36r1Kt3OSj7EyBQX # oZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8VlzNmoxaGMny3YnGir5W # f6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOhMBs9M8XL223F # g47xlGsMXdfuY-4jaqVw", # "iv": "bbd5sTkYwhAIqfHsx8DayA", # "ciphertext": "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62 # JhJvGZ4_FNVSiGc_raa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wn # I3Jvo0mkpEEnlDmZvDu_k8OWzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc # 2VbVbK4dQKPdNTjPPEmRqcaGeTWZVyeSUvf5k59yJZxRuSvWFf6KrNtm # RdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0zT5CbL5Qlw3sRc7u_hg0y # KVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2O6_7uInMGhFeX4c # tHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VWi7lzA6BP4 # 30m", # "tag": "kvKuFBXHe5mQr4lqgobAUg" # } # Figure 83: Flattened JWE JSON Serialization # 5.2. Key Encryption Using RSA-OAEP with AES-GCM # This example illustrates encrypting content using the "RSA-OAEP" # (RSAES-OAEP) key encryption algorithm and the "A256GCM" (AES-GCM) # content encryption algorithm. # Note that RSAES-OAEP uses random data to generate the ciphertext; it # might not be possible to exactly replicate the results in this # section. # Note that only the RSA public key is necessary to perform the # encryption. However, the example includes the RSA private key to # allow readers to validate the output. # Note that whitespace is added for readability as described in # Section 1.1. - - - - - - # Miller Informational [Page 45] # RFC 7520 JOSE Cookbook May 2015 # 5.2.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the Plaintext from Figure 72. # o RSA public key; this example uses the key from Figure 84. # o "alg" parameter of "RSA-OAEP". # o "enc" parameter of "A256GCM". # { # "kty": "RSA", # "kid": "samwise.gamgee@hobbiton.example", # "use": "enc", # "n": "wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRr # I4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-Fy # XJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnk # Nrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeSt # sqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M # 5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIU # e7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBOD # FskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb # 86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqB # SAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhO # OnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDa # iCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnT # yC0xhWBlsolZE", # "e": "AQAB", # "alg": "RSA-OAEP", # "d": "n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bx # cc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq # -B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT # 2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9E # A-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876 # DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIj # h1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r # -MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yD # F-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1L # oomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W # _IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28 # S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c # 9WsWgRzI-K8gE", # "p": "7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKgh # vM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsY # a_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3m # Y46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma- - # Miller Informational [Page 46] # RFC 7520 JOSE Cookbook May 2015 # RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9s # fbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgP # gWCv5HoQ", # "q": "zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6Zy # KQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDc # qssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYG # RuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJ # aPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EX # e2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJ # JlXXnH8Q", # "dp": "19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xn # x5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQ # J_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72F # ZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3i # XjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGm # pKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9Lc # nwwT0jvoQ", # "dq": "S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1 # VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM # 1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fg # dyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrI # ChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2 # AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iz # nBNCeOUIQ", # "qi": "FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCc # iRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80 # oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMw # QqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl # 27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4 # UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq # 8EzqZEKIA" # } # Figure 84: RSA 4096-Bit Key # (NOTE: While the key includes the private parameters, only the public # parameters "e" and "n" are necessary for the encryption operation.) # 5.2.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 85. # o Initialization Vector; this example uses the Initialization Vector # from Figure 86. - - # Miller Informational [Page 47] # RFC 7520 JOSE Cookbook May 2015 # mYMfsggkTAm0TbvtlFh2hyoXnbEzJQjMxmgLN3d8xXA # Figure 85: Content Encryption Key, base64url-encoded # -nBoKLH0YkLZPSI9 # Figure 86: Initialization Vector, base64url-encoded # 5.2.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 85) with # the RSA key (Figure 84) produces the following Encrypted Key: # rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQi # beYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyu # cvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58 # -Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8Bpx # KdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pK # IIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7 # pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQ # fOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe3 # 8UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU # 06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5 # Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDR # s # Figure 87: Encrypted Key, base64url-encoded # 5.2.4. Encrypting the Content # The following is generated before encrypting the Plaintext: # o JWE Protected Header; this example uses the header from Figure 88, # encoded using base64url [RFC4648] to produce Figure 89. # { # "alg": "RSA-OAEP", # "kid": "samwise.gamgee@hobbiton.example", # "enc": "A256GCM" # } # Figure 88: JWE Protected Header JSON # eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG # 9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0 # Figure 89: JWE Protected Header, base64url-encoded - - # Miller Informational [Page 48] # RFC 7520 JOSE Cookbook May 2015 # Performing the content encryption operation over the Plaintext # (Figure 72) with the following: # o CEK (Figure 85); # o Initialization Vector (Figure 86); and # o JWE Protected Header (Figure 89) as authenticated data # produces the following: # o Ciphertext from Figure 90. # o Authentication Tag from Figure 91. # o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgR # L-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEw # P7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8 # iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML # 7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSV # maPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw # Figure 90: Ciphertext, base64url-encoded # UCGiqJxhBI3IFVdPalHHvA # Figure 91: Authentication Tag, base64url-encoded # 5.2.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 89) # o Encrypted Key (Figure 87) # o Initialization Vector (Figure 86) # o Ciphertext (Figure 90) # o Authentication Tag (Figure 91) - - - - - - - - # Miller Informational [Page 49] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG # 9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0 # . # rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQi # beYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyu # cvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58 # -Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8Bpx # KdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pK # IIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7 # pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQ # fOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe3 # 8UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU # 06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5 # Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDR # s # . # -nBoKLH0YkLZPSI9 # . # o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgR # L-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEw # P7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8 # iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML # 7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSV # maPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw # . # UCGiqJxhBI3IFVdPalHHvA # Figure 92: JWE Compact Serialization - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 50] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNu # h7lCiud48LxeolRdtFF4nzQibeYOl5S_PJsAXZwSXtDePz9hk-Bb # tsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyucvI6hvALeZ6OGnhNV4 # v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58-Aad3FzM # uo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8B # pxKdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1 # asnuHtVMt2pKIIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq # 5pGqFmW2k8zpO878TRlZx7pZfPYDSXZyS0CfKKkMozT_qiCwZTSz # 4duYnt8hS4Z9sGthXn9uDqd6wycMagnQfOTs_lycTWmY-aqWVDKh # jYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe38UjQb0lvXn # 1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU # 06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8a # KaOnx6ASE5Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xD # EdHAVCGRzN3woEI2ozDRs" # } # ], # "protected": "eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2 # FtZ2VlQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0", # "iv": "-nBoKLH0YkLZPSI9", # "ciphertext": "o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6 # UJuJowOHC5ytjqYgRL-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYx # rXfVzIIaRdhYtEMRBvBWbEwP7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lh # hNcizPersuhw5f-pGYzseva-TUaL8iWnctc-sSwy7SQmRkfhDjwbz0fz # 6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML7Cc2GxgvI7zqWo0YIEc7a # CflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSVmaPpOslY2n525Dx # DfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw", # "tag": "UCGiqJxhBI3IFVdPalHHvA" # } # Figure 93: General JWE JSON Serialization - - - - - - - - - - - - - - # Miller Informational [Page 51] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2 # FtZ2VlQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0", # "encrypted_key": "rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lC # iud48LxeolRdtFF4nzQibeYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2U # sPOdwjC9NhNupNNu9uHIVftDyucvI6hvALeZ6OGnhNV4v1zx2k7O1D89 # mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58-Aad3FzMuo3Fn9buEP2yXakL # XYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8BpxKdUV9ScfJQTcYm6eJE # Bz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pKIIfux5BC6huI # vmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7pZfPYD # SXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQ # fOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO # 2AWBe38UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G # 7S2rscw5lQQU06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDP # Tr6Cbo8aKaOnx6ASE5Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ # 69xDEdHAVCGRzN3woEI2ozDRs", # "iv": "-nBoKLH0YkLZPSI9", # "ciphertext": "o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6 # UJuJowOHC5ytjqYgRL-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYx # rXfVzIIaRdhYtEMRBvBWbEwP7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lh # hNcizPersuhw5f-pGYzseva-TUaL8iWnctc-sSwy7SQmRkfhDjwbz0fz # 6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML7Cc2GxgvI7zqWo0YIEc7a # CflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSVmaPpOslY2n525Dx # DfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw", # "tag": "UCGiqJxhBI3IFVdPalHHvA" # } # Figure 94: Flattened JWE JSON Serialization # 5.3. Key Wrap Using PBES2-AES-KeyWrap with AES-CBC-HMAC-SHA2 # The example illustrates encrypting content using the # "PBES2-HS512+A256KW" (PBES2 Password-based Encryption using HMAC- # SHA-512 and AES-256-KeyWrap) key encryption algorithm with the # "A128CBC-HS256" (AES-128-CBC-HMAC-SHA-256) content encryption # algorithm. # A common use of password-based encryption is the import/export of # keys. Therefore, this example uses a JWK Set for the Plaintext # content instead of the Plaintext from Figure 72. - - - - - - - # Miller Informational [Page 52] # RFC 7520 JOSE Cookbook May 2015 # Note that if password-based encryption is used for multiple # recipients, it is expected that each recipient use different values # for the PBES2 parameters "p2s" and "p2c". # Note that whitespace is added for readability as described in # Section 1.1. # 5.3.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the Plaintext from Figure 95 # (NOTE: All whitespace was added for readability). # o Password; this example uses the password from Figure 96 -- with # the sequence "\xe2\x80\x93" replaced with (U+2013 EN DASH). # o "alg" parameter of "PBES2-HS512+A256KW". # o "enc" parameter of "A128CBC-HS256". # { # "keys": [ # { # "kty": "oct", # "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", # "use": "enc", # "alg": "A128GCM", # "k": "XctOhJAkA-pD9Lh7ZgW_2A" # }, # { # "kty": "oct", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "use": "enc", # "alg": "A128KW", # "k": "GZy6sIZ6wl9NJOKB-jnmVQ" # }, # { # "kty": "oct", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "use": "enc", # "alg": "A256GCMKW", # "k": "qC57l_uxcm7Nm3K-ct4GFjx8tM1U8CZ0NLBvdQstiS8" # } # ] # } # Figure 95: Plaintext Content - # Miller Informational [Page 53] # RFC 7520 JOSE Cookbook May 2015 # entrap_o\xe2\x80\x93peter_long\xe2\x80\x93credit_tun # Figure 96: Password # 5.3.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 97. # o Initialization Vector; this example uses the Initialization Vector # from Figure 98. # uwsjJXaBK407Qaf0_zpcpmr1Cs0CC50hIUEyGNEt3m0 # Figure 97: Content Encryption Key, base64url-encoded # VBiCzVHNoLiR3F4V82uoTQ # Figure 98: Initialization Vector, base64url-encoded # 5.3.3. Encrypting the Key # The following are generated before encrypting the CEK: # o Salt input; this example uses the salt input from Figure 99. # o Iteration count; this example uses the iteration count 8192. # 8Q1SzinasR3xchYz6ZZcHA # Figure 99: Salt Input, base64url-encoded # Performing the key encryption operation over the CEK (Figure 97) with # the following: # o Password (Figure 96); # o Salt input (Figure 99), encoded as an octet string; and # o Iteration count (8192) # produces the following Encrypted Key: # d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlUtFPWdgtURtmeDV1g # Figure 100: Encrypted Key, base64url-encoded - # Miller Informational [Page 54] # RFC 7520 JOSE Cookbook May 2015 # 5.3.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 101, encoded using base64url [RFC4648] to produce # Figure 102. # { # "alg": "PBES2-HS512+A256KW", # "p2s": "8Q1SzinasR3xchYz6ZZcHA", # "p2c": 8192, # "cty": "jwk-set+json", # "enc": "A128CBC-HS256" # } # Figure 101: JWE Protected Header JSON # eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3 # hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOiJqd2stc2V0K2pzb24iLCJl # bmMiOiJBMTI4Q0JDLUhTMjU2In0 # Figure 102: JWE Protected Header, base64url-encoded # Performing the content encryption operation over the Plaintext # (Figure 95) with the following: # o CEK (Figure 97); # o Initialization Vector (Figure 98); and # o JWE Protected Header (Figure 102) as authenticated data # produces the following: # o Ciphertext from Figure 103. # o Authentication Tag from Figure 104. - - - - - - - - - - - # Miller Informational [Page 55] # RFC 7520 JOSE Cookbook May 2015 # 23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2nsnGIX86vMXqIi6IR # sfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYCNA_XOmzg8yZR9oyjo6l # TF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3dwejXBtIodN84PeqMb # 6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL93R3fOoOJbmk2GBQZL # _SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKd # PQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrok # AKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N- # zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdTw8V # 3kobXZ77ulMwDs4p # Figure 103: Ciphertext, base64url-encoded # 0HlwodAhOCILG5SQ2LQ9dg # Figure 104: Authentication Tag, base64url-encoded # 5.3.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 102) # o Encrypted Key (Figure 100) # o Initialization Vector (Figure 98) # o Ciphertext (Figure 103) # o Authentication Tag (Figure 104) - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 56] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3 # hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOiJqd2stc2V0K2pzb24iLCJl # bmMiOiJBMTI4Q0JDLUhTMjU2In0 # . # d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlUtFPWdgtURtmeDV1g # . # VBiCzVHNoLiR3F4V82uoTQ # . # 23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2nsnGIX86vMXqIi6IR # sfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYCNA_XOmzg8yZR9oyjo6l # TF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3dwejXBtIodN84PeqMb # 6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL93R3fOoOJbmk2GBQZL # _SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKd # PQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrok # AKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N- # zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdTw8V # 3kobXZ77ulMwDs4p # . # 0HlwodAhOCILG5SQ2LQ9dg # Figure 105: JWE Compact Serialization - - - - - - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 57] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlU # tFPWdgtURtmeDV1g" # } # ], # "protected": "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOi # I4UTFTemluYXNSM3hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOi # Jqd2stc2V0K2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", # "iv": "VBiCzVHNoLiR3F4V82uoTQ", # "ciphertext": "23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2 # nsnGIX86vMXqIi6IRsfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpD # jEYCNA_XOmzg8yZR9oyjo6lTF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_ # hkBsnuoqoM3dwejXBtIodN84PeqMb6asmas_dpSsz7H10fC5ni9xIz42 # 4givB1YLldF6exVmL93R3fOoOJbmk2GBQZL_SEGllv2cQsBgeprARsaQ # 7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKdPQMTlVJKkqtV4Ru # 5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrokAKYPqmXUe # RdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N-zl5 # tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdT # w8V3kobXZ77ulMwDs4p", # "tag": "0HlwodAhOCILG5SQ2LQ9dg" # } # Figure 106: General JWE JSON Serialization - - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 58] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOi # I4UTFTemluYXNSM3hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOi # Jqd2stc2V0K2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", # "encrypted_key": "d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlUtFPW # dgtURtmeDV1g", # "iv": "VBiCzVHNoLiR3F4V82uoTQ", # "ciphertext": "23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2 # nsnGIX86vMXqIi6IRsfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpD # jEYCNA_XOmzg8yZR9oyjo6lTF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_ # hkBsnuoqoM3dwejXBtIodN84PeqMb6asmas_dpSsz7H10fC5ni9xIz42 # 4givB1YLldF6exVmL93R3fOoOJbmk2GBQZL_SEGllv2cQsBgeprARsaQ # 7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKdPQMTlVJKkqtV4Ru # 5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrokAKYPqmXUe # RdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N-zl5 # tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdT # w8V3kobXZ77ulMwDs4p", # "tag": "0HlwodAhOCILG5SQ2LQ9dg" # } # Figure 107: Flattened JWE JSON Serialization # 5.4. Key Agreement with Key Wrapping Using ECDH-ES and AES-KeyWrap with # AES-GCM # This example illustrates encrypting content using the "ECDH- # ES+A128KW" (Elliptic Curve Diffie-Hellman Ephemeral-Static with AES- # 128-KeyWrap) key encryption algorithm and the "A128GCM" (AES-GCM) # content encryption algorithm. # Note that only the EC public key is necessary to perform the key # agreement. However, the example includes the EC private key to allow # readers to validate the output. # Note that whitespace is added for readability as described in # Section 1.1. # 5.4.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o EC public key; this example uses the public key from Figure 108. - - - # Miller Informational [Page 59] # RFC 7520 JOSE Cookbook May 2015 # o "alg" parameter of "ECDH-ES+A128KW". # o "enc" parameter of "A128GCM". # { # "kty": "EC", # "kid": "peregrin.took@tuckborough.example", # "use": "enc", # "crv": "P-384", # "x": "YU4rRUzdmVqmRtWOs2OpDE_T5fsNIodcG8G5FWPrTPMyxpzsSOGaQL # pe2FpxBmu2", # "y": "A8-yxCHxkfBz3hKZfI1jUYMjUhsEveZ9THuwFjH2sCNdtksRJU7D5- # SkgaFL1ETP", # "d": "iTx2pk7wW-GqJkHcEkFQb2EFyYcO7RugmaW3mRrQVAOUiPommT0Idn # YK2xDlZh-j" # } # Figure 108: Elliptic Curve P-384 Key, in JWK Format # (NOTE: While the key includes the private parameters, only the public # parameters "crv", "x", and "y" are necessary for the encryption # operation.) # 5.4.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 109. # o Initialization Vector; this example uses the Initialization Vector # from Figure 110. # Nou2ueKlP70ZXDbq9UrRwg # Figure 109: Content Encryption Key, base64url-encoded # mH-G2zVqgztUtnW_ # Figure 110: Initialization Vector, base64url-encoded # 5.4.3. Encrypting the Key # To encrypt the Content Encryption Key, the following is generated: # o Ephemeral EC private key on the same curve as the EC public key; # this example uses the private key from Figure 111. - - # Miller Informational [Page 60] # RFC 7520 JOSE Cookbook May 2015 # { # "kty": "EC", # "crv": "P-384", # "x": "uBo4kHPw6kbjx5l0xowrd_oYzBmaz-GKFZu4xAFFkbYiWgutEK6iuE # DsQ6wNdNg3", # "y": "sp3p5SGhZVC2faXumI-e9JU2Mo8KpoYrFDr5yPNVtW4PgEwZOyQTA- # JdaY8tb7E0", # "d": "D5H4Y_5PSKZvhfVFbcCYJOtcGZygRgfZkpsBr59Icmmhe9sW6nkZ8W # fwhinUfWJg" # } # Figure 111: Ephemeral Elliptic Curve P-384 Key, in JWK Format # Performing the key encryption operation over the CEK (Figure 109) # with the following: # o The static Elliptic Curve public key (Figure 108); and # o The ephemeral Elliptic Curve private key (Figure 111) # produces the following JWE Encrypted Key: # 0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2 # Figure 112: Encrypted Key, base64url-encoded # 5.4.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 113, encoded to base64url [RFC4648] as Figure 114. # { # "alg": "ECDH-ES+A128KW", # "kid": "peregrin.took@tuckborough.example", # "epk": { # "kty": "EC", # "crv": "P-384", # "x": "uBo4kHPw6kbjx5l0xowrd_oYzBmaz-GKFZu4xAFFkbYiWgutEK6i # uEDsQ6wNdNg3", # "y": "sp3p5SGhZVC2faXumI-e9JU2Mo8KpoYrFDr5yPNVtW4PgEwZOyQT # A-JdaY8tb7E0" # }, # "enc": "A128GCM" # } # Figure 113: JWE Protected Header JSON - # Miller Informational [Page 61] # RFC 7520 JOSE Cookbook May 2015 # eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdH # Vja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAt # Mzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NH # hBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMy # ZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWT # h0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0 # Figure 114: JWE Protected Header, base64url-encoded # Performing the content encryption operation on the Plaintext # (Figure 72) using the following: # o CEK (Figure 109); # o Initialization Vector (Figure 110); and # o JWE Protected Header (Figure 114) as authenticated data # produces the following: # o Ciphertext from Figure 115. # o Authentication Tag from Figure 116. # tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cP # WJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0 # IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkc # Y9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w0 # 3XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu # 07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ # Figure 115: Ciphertext, base64url-encoded # WuGzxmcreYjpHGJoa17EBg # Figure 116: Authentication Tag, base64url-encoded - - - - - - - - - - - - - # Miller Informational [Page 62] # RFC 7520 JOSE Cookbook May 2015 # 5.4.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 114) # o Encrypted Key (Figure 112) # o Initialization Vector (Figure 110) # o Ciphertext (Figure 115) # o Authentication Tag (Figure 116) # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdH # Vja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAt # Mzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NH # hBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMy # ZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWT # h0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0 # . # 0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2 # . # mH-G2zVqgztUtnW_ # . # tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cP # WJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0 # IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkc # Y9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w0 # 3XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu # 07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ # . # WuGzxmcreYjpHGJoa17EBg # Figure 117: JWE Compact Serialization - - - - - - - - - - - - # Miller Informational [Page 63] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2" # } # ], # "protected": "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcm # VncmluLnRvb2tAdHVja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdH # kiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InVCbzRrSFB3Nmtiang1bD # B4b3dyZF9vWXpCbWF6LUdLRlp1NHhBRkZrYllpV2d1dEVLNml1RURzUT # Z3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMyZmFYdW1JLWU5SlUyTW84S3 # BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWTh0YjdFMCJ9LCJlbm # MiOiJBMTI4R0NNIn0", # "iv": "mH-G2zVqgztUtnW_", # "ciphertext": "tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz # 5NJ76oID7lpnAi_cPWJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzs # XaEwDdXta9Mn5B7cCBoJKB0IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05 # jMwbKkTe2yK3mjF6SBAsgicQDVCkcY9BLluzx1RmC3ORXaM0JaHPB93Y # cdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w03XdLkjXIuEr2hWgeP-nkU # ZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu07WNhjzJEPc4jVn # tRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ", # "tag": "WuGzxmcreYjpHGJoa17EBg" # } # Figure 118: General JWE JSON Serialization - - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 64] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcm # VncmluLnRvb2tAdHVja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdH # kiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InVCbzRrSFB3Nmtiang1bD # B4b3dyZF9vWXpCbWF6LUdLRlp1NHhBRkZrYllpV2d1dEVLNml1RURzUT # Z3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMyZmFYdW1JLWU5SlUyTW84S3 # BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWTh0YjdFMCJ9LCJlbm # MiOiJBMTI4R0NNIn0", # "encrypted_key": "0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2", # "iv": "mH-G2zVqgztUtnW_", # "ciphertext": "tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz # 5NJ76oID7lpnAi_cPWJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzs # XaEwDdXta9Mn5B7cCBoJKB0IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05 # jMwbKkTe2yK3mjF6SBAsgicQDVCkcY9BLluzx1RmC3ORXaM0JaHPB93Y # cdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w03XdLkjXIuEr2hWgeP-nkU # ZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu07WNhjzJEPc4jVn # tRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ", # "tag": "WuGzxmcreYjpHGJoa17EBg" # } # Figure 119: Flattened JWE JSON Serialization # 5.5. Key Agreement Using ECDH-ES with AES-CBC-HMAC-SHA2 # This example illustrates encrypting content using the "ECDH-ES" # (Elliptic Curve Diffie-Hellman Ephemeral-Static) key agreement # algorithm and the "A128CBC-HS256" (AES-128-CBC-HMAC-SHA-256) content # encryption algorithm. # Note that only the EC public key is necessary to perform the key # agreement. However, the example includes the EC private key to allow # readers to validate the output. # Note that whitespace is added for readability as described in # Section 1.1. - - - - - - - - - - - - # Miller Informational [Page 65] # RFC 7520 JOSE Cookbook May 2015 # 5.5.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o EC public key; this example uses the public key from Figure 120. # o "alg" parameter of "ECDH-ES". # o "enc" parameter of "A128CBC-HS256". # { # "kty": "EC", # "kid": "meriadoc.brandybuck@buckland.example", # "use": "enc", # "crv": "P-256", # "x": "Ze2loSV3wrroKUN_4zhwGhCqo3Xhu1td4QjeQ5wIVR0", # "y": "HlLtdXARY_f55A3fnzQbPcm6hgr34Mp8p-nuzQCE0Zw", # "d": "r_kHyZ-a06rmxM3yESK84r1otSg-aQcVStkRhA-iCM8" # } # Figure 120: Elliptic Curve P-256 Key # (NOTE: While the key includes the private parameters, only the public # parameters "crv", "x", and "y" are necessary for the encryption # operation.) # 5.5.2. Generated Factors # The following is generated before encrypting: # o Initialization Vector; this example uses the Initialization Vector # from Figure 121. # yc9N8v5sYyv3iGQT926IUg # Figure 121: Initialization Vector, base64url-encoded # NOTE: The Content Encryption Key (CEK) is not randomly generated; # instead, it is determined using ECDH-ES key agreement. - - - - - - - - # Miller Informational [Page 66] # RFC 7520 JOSE Cookbook May 2015 # 5.5.3. Key Agreement # The following is generated to agree on a CEK: # o Ephemeral private key; this example uses the private key from # Figure 122. # { # "kty": "EC", # "crv": "P-256", # "x": "mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA", # "y": "8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs", # "d": "AtH35vJsQ9SGjYfOsjUxYXQKrPH3FjZHmEtSKoSN8cM" # } # Figure 122: Ephemeral Private Key, in JWK Format # Performing the ECDH operation using the static EC public key # (Figure 120) over the ephemeral private key (Figure 122) produces the # following CEK: # hzHdlfQIAEehb8Hrd_mFRhKsKLEzPfshfXs9l6areCc # Figure 123: Agreed-to Content Encryption Key, base64url-encoded # 5.5.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 124, encoded to base64url [RFC4648] as Figure 125. # { # "alg": "ECDH-ES", # "kid": "meriadoc.brandybuck@buckland.example", # "epk": { # "kty": "EC", # "crv": "P-256", # "x": "mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA", # "y": "8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs" # }, # "enc": "A128CBC-HS256" # } # Figure 124: JWE Protected Header JSON - - - - # Miller Informational [Page 67] # RFC 7520 JOSE Cookbook May 2015 # eyJhbGciOiJFQ0RILUVTIiwia2lkIjoibWVyaWFkb2MuYnJhbmR5YnVja0BidW # NrbGFuZC5leGFtcGxlIiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYi # LCJ4IjoibVBVS1RfYkFXR0hJaGcwVHBqanFWc1AxclhXUXVfdndWT0hIdE5rZF # lvQSIsInkiOiI4QlFBc0ltR2VBUzQ2ZnlXdzVNaFlmR1RUMElqQnBGdzJTUzM0 # RHY0SXJzIn0sImVuYyI6IkExMjhDQkMtSFMyNTYifQ # Figure 125: JWE Protected Header, base64url-encoded # Performing the content encryption operation on the Plaintext # (Figure 72) using the following: # o CEK (Figure 123); # o Initialization Vector (Figure 121); and # o JWE Protected Header (Figure 125) as authenticated data # produces the following: # o Ciphertext from Figure 126. # o Authentication Tag from Figure 127. # BoDlwPnTypYq-ivjmQvAYJLb5Q6l-F3LIgQomlz87yW4OPKbWE1zSTEFjDfhU9 # IPIOSA9Bml4m7iDFwA-1ZXvHteLDtw4R1XRGMEsDIqAYtskTTmzmzNa-_q4F_e # vAPUmwlO-ZG45Mnq4uhM1fm_D9rBtWolqZSF3xGNNkpOMQKF1Cl8i8wjzRli7- # IXgyirlKQsbhhqRzkv8IcY6aHl24j03C-AR2le1r7URUhArM79BY8soZU0lzwI # -sD5PZ3l4NDCCei9XkoIAfsXJWmySPoeRb2Ni5UZL4mYpvKDiwmyzGd65KqVw7 # MsFfI_K767G9C9Azp73gKZD0DyUn1mn0WW5LmyX_yJ-3AROq8p1WZBfG-ZyJ61 # 95_JGG2m9Csg # Figure 126: Ciphertext, base64url-encoded # WCCkNa-x4BeB9hIDIfFuhg # Figure 127: Authentication Tag, base64url-encoded # 5.5.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 114) # o Initialization Vector (Figure 110) # o Ciphertext (Figure 115) # o Authentication Tag (Figure 116) - # Miller Informational [Page 68] # RFC 7520 JOSE Cookbook May 2015 # Only the general JWE JSON Serialization is presented because the # flattened JWE JSON Serialization is identical. # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJFQ0RILUVTIiwia2lkIjoibWVyaWFkb2MuYnJhbmR5YnVja0BidW # NrbGFuZC5leGFtcGxlIiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYi # LCJ4IjoibVBVS1RfYkFXR0hJaGcwVHBqanFWc1AxclhXUXVfdndWT0hIdE5rZF # lvQSIsInkiOiI4QlFBc0ltR2VBUzQ2ZnlXdzVNaFlmR1RUMElqQnBGdzJTUzM0 # RHY0SXJzIn0sImVuYyI6IkExMjhDQkMtSFMyNTYifQ # . # . # yc9N8v5sYyv3iGQT926IUg # . # BoDlwPnTypYq-ivjmQvAYJLb5Q6l-F3LIgQomlz87yW4OPKbWE1zSTEFjDfhU9 # IPIOSA9Bml4m7iDFwA-1ZXvHteLDtw4R1XRGMEsDIqAYtskTTmzmzNa-_q4F_e # vAPUmwlO-ZG45Mnq4uhM1fm_D9rBtWolqZSF3xGNNkpOMQKF1Cl8i8wjzRli7- # IXgyirlKQsbhhqRzkv8IcY6aHl24j03C-AR2le1r7URUhArM79BY8soZU0lzwI # -sD5PZ3l4NDCCei9XkoIAfsXJWmySPoeRb2Ni5UZL4mYpvKDiwmyzGd65KqVw7 # MsFfI_K767G9C9Azp73gKZD0DyUn1mn0WW5LmyX_yJ-3AROq8p1WZBfG-ZyJ61 # 95_JGG2m9Csg # . # WCCkNa-x4BeB9hIDIfFuhg # Figure 128: JWE Compact Serialization # The resulting JWE object using the general JWE JSON Serialization: # { # "protected": "eyJhbGciOiJFQ0RILUVTIiwia2lkIjoibWVyaWFkb2MuYn # JhbmR5YnVja0BidWNrbGFuZC5leGFtcGxlIiwiZXBrIjp7Imt0eSI6Ik # VDIiwiY3J2IjoiUC0yNTYiLCJ4IjoibVBVS1RfYkFXR0hJaGcwVHBqan # FWc1AxclhXUXVfdndWT0hIdE5rZFlvQSIsInkiOiI4QlFBc0ltR2VBUz # Q2ZnlXdzVNaFlmR1RUMElqQnBGdzJTUzM0RHY0SXJzIn0sImVuYyI6Ik # ExMjhDQkMtSFMyNTYifQ", # "iv": "yc9N8v5sYyv3iGQT926IUg", # "ciphertext": "BoDlwPnTypYq-ivjmQvAYJLb5Q6l-F3LIgQomlz87yW4O # PKbWE1zSTEFjDfhU9IPIOSA9Bml4m7iDFwA-1ZXvHteLDtw4R1XRGMEs # DIqAYtskTTmzmzNa-_q4F_evAPUmwlO-ZG45Mnq4uhM1fm_D9rBtWolq # ZSF3xGNNkpOMQKF1Cl8i8wjzRli7-IXgyirlKQsbhhqRzkv8IcY6aHl2 # 4j03C-AR2le1r7URUhArM79BY8soZU0lzwI-sD5PZ3l4NDCCei9XkoIA # fsXJWmySPoeRb2Ni5UZL4mYpvKDiwmyzGd65KqVw7MsFfI_K767G9C9A # zp73gKZD0DyUn1mn0WW5LmyX_yJ-3AROq8p1WZBfG-ZyJ6195_JGG2m9 # Csg", # "tag": "WCCkNa-x4BeB9hIDIfFuhg" # } # Figure 129: General JWE JSON Serialization - # Miller Informational [Page 69] # RFC 7520 JOSE Cookbook May 2015 # 5.6. Direct Encryption Using AES-GCM # This example illustrates encrypting content using a previously # exchanged key directly and the "A128GCM" (AES-GCM) content encryption # algorithm. # Note that whitespace is added for readability as described in # Section 1.1. # 5.6.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 130. # o "alg" parameter of "dir". # o "enc" parameter of "A128GCM". # { # "kty": "oct", # "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", # "use": "enc", # "alg": "A128GCM", # "k": "XctOhJAkA-pD9Lh7ZgW_2A" # } # Figure 130: AES 128-Bit Key, in JWK Format # 5.6.2. Generated Factors # The following is generated before encrypting: # o Initialization Vector; this example uses the Initialization Vector # from Figure 131. # refa467QzzKx6QAB # Figure 131: Initialization Vector, base64url-encoded - - - - - - - # Miller Informational [Page 70] # RFC 7520 JOSE Cookbook May 2015 # 5.6.3. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 132, encoded as base64url [RFC4648] to produce Figure 133. # { # "alg": "dir", # "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", # "enc": "A128GCM" # } # Figure 132: JWE Protected Header JSON # eyJhbGciOiJkaXIiLCJraWQiOiI3N2M3ZTJiOC02ZTEzLTQ1Y2YtODY3Mi02MT # diNWI0NTI0M2EiLCJlbmMiOiJBMTI4R0NNIn0 # Figure 133: JWE Protected Header, base64url-encoded # Performing the encryption operation on the Plaintext (Figure 72) # using the following: # o CEK (Figure 130); # o Initialization Vector (Figure 131); and # o JWE Protected Header (Figure 133) as authenticated data # produces the following: # o Ciphertext from Figure 134. # o Authentication Tag from Figure 135. # JW_i_f52hww_ELQPGaYyeAB6HYGcR559l9TYnSovc23XJoBcW29rHP8yZOZG7Y # hLpT1bjFuvZPjQS-m0IFtVcXkZXdH_lr_FrdYt9HRUYkshtrMmIUAyGmUnd9zM # DB2n0cRDIHAzFVeJUDxkUwVAE7_YGRPdcqMyiBoCO-FBdE-Nceb4h3-FtBP-c_ # BIwCPTjb9o0SbdcdREEMJMyZBH8ySWMVi1gPD9yxi-aQpGbSv_F9N4IZAxscj5 # g-NJsUPbjk29-s7LJAGb15wEBtXphVCgyy53CoIKLHHeJHXex45Uz9aKZSRSIn # ZI-wjsY0yu3cT4_aQ3i1o-tiE-F8Ios61EKgyIQ4CWao8PFMj8TTnp # Figure 134: Ciphertext, base64url-encoded # vbb32Xvllea2OtmHAdccRQ # Figure 135: Authentication Tag, base64url-encoded - - # Miller Informational [Page 71] # RFC 7520 JOSE Cookbook May 2015 # 5.6.4. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 133) # o Initialization Vector (Figure 131) # o Ciphertext (Figure 134) # o Authentication Tag (Figure 135) # Only the general JWE JSON Serialization is presented because the # flattened JWE JSON Serialization is identical. # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJkaXIiLCJraWQiOiI3N2M3ZTJiOC02ZTEzLTQ1Y2YtODY3Mi02MT # diNWI0NTI0M2EiLCJlbmMiOiJBMTI4R0NNIn0 # . # . # refa467QzzKx6QAB # . # JW_i_f52hww_ELQPGaYyeAB6HYGcR559l9TYnSovc23XJoBcW29rHP8yZOZG7Y # hLpT1bjFuvZPjQS-m0IFtVcXkZXdH_lr_FrdYt9HRUYkshtrMmIUAyGmUnd9zM # DB2n0cRDIHAzFVeJUDxkUwVAE7_YGRPdcqMyiBoCO-FBdE-Nceb4h3-FtBP-c_ # BIwCPTjb9o0SbdcdREEMJMyZBH8ySWMVi1gPD9yxi-aQpGbSv_F9N4IZAxscj5 # g-NJsUPbjk29-s7LJAGb15wEBtXphVCgyy53CoIKLHHeJHXex45Uz9aKZSRSIn # ZI-wjsY0yu3cT4_aQ3i1o-tiE-F8Ios61EKgyIQ4CWao8PFMj8TTnp # . # vbb32Xvllea2OtmHAdccRQ # Figure 136: JWE Compact Serialization - - - - - - - - - - - - - - - - # Miller Informational [Page 72] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "protected": "eyJhbGciOiJkaXIiLCJraWQiOiI3N2M3ZTJiOC02ZTEzLT # Q1Y2YtODY3Mi02MTdiNWI0NTI0M2EiLCJlbmMiOiJBMTI4R0NNIn0", # "iv": "refa467QzzKx6QAB", # "ciphertext": "JW_i_f52hww_ELQPGaYyeAB6HYGcR559l9TYnSovc23XJ # oBcW29rHP8yZOZG7YhLpT1bjFuvZPjQS-m0IFtVcXkZXdH_lr_FrdYt9 # HRUYkshtrMmIUAyGmUnd9zMDB2n0cRDIHAzFVeJUDxkUwVAE7_YGRPdc # qMyiBoCO-FBdE-Nceb4h3-FtBP-c_BIwCPTjb9o0SbdcdREEMJMyZBH8 # ySWMVi1gPD9yxi-aQpGbSv_F9N4IZAxscj5g-NJsUPbjk29-s7LJAGb1 # 5wEBtXphVCgyy53CoIKLHHeJHXex45Uz9aKZSRSInZI-wjsY0yu3cT4_ # aQ3i1o-tiE-F8Ios61EKgyIQ4CWao8PFMj8TTnp", # "tag": "vbb32Xvllea2OtmHAdccRQ" # } # Figure 137: General JWE JSON Serialization # 5.7. Key Wrap Using AES-GCM KeyWrap with AES-CBC-HMAC-SHA2 # This example illustrates encrypting content using the "A256GCMKW" # (AES-256-GCM-KeyWrap) key encryption algorithm with the "A128CBC- # HS256" (AES-128-CBC-HMAC-SHA-256) content encryption algorithm. # Note that whitespace is added for readability as described in # Section 1.1. # 5.7.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o AES symmetric key; this example uses the key from Figure 138. # o "alg" parameter of "A256GCMKW". # o "enc" parameter of "A128CBC-HS256". - - - - - - - - - - - # Miller Informational [Page 73] # RFC 7520 JOSE Cookbook May 2015 # { # "kty": "oct", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "use": "enc", # "alg": "A256GCMKW", # "k": "qC57l_uxcm7Nm3K-ct4GFjx8tM1U8CZ0NLBvdQstiS8" # } # Figure 138: AES 256-Bit Key # 5.7.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 139. # o Initialization Vector for content encryption; this example uses # the Initialization Vector from Figure 140. # UWxARpat23nL9ReIj4WG3D1ee9I4r-Mv5QLuFXdy_rE # Figure 139: Content Encryption Key, base64url-encoded # gz6NjyEFNm_vm8Gj6FwoFQ # Figure 140: Initialization Vector, base64url-encoded # 5.7.3. Encrypting the Key # The following is generated before encrypting the CEK: # o Initialization Vector for key wrapping; this example uses the # Initialization Vector from Figure 141. # KkYT0GX_2jHlfqN_ # Figure 141: Initialization Vector for Key Wrapping, base64url-encoded - - - - - - - - - - - # Miller Informational [Page 74] # RFC 7520 JOSE Cookbook May 2015 # Performing the key encryption operation over the CEK (Figure 139) # with the following: # o AES symmetric key (Figure 138); # o Initialization Vector (Figure 141); and # o The empty string as authenticated data # produces the following: # o Encrypted Key from Figure 142. # o Authentication Tag from Figure 143. # lJf3HbOApxMEBkCMOoTnnABxs_CvTWUmZQ2ElLvYNok # Figure 142: Encrypted Key, base64url-encoded # kfPduVQ3T3H6vnewt--ksw # Figure 143: Authentication Tag from Key Wrapping, base64url-encoded # 5.7.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 144, encoded to base64url [RFC4648] as Figure 145. # { # "alg": "A256GCMKW", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "tag": "kfPduVQ3T3H6vnewt--ksw", # "iv": "KkYT0GX_2jHlfqN_", # "enc": "A128CBC-HS256" # } # Figure 144: JWE Protected Header JSON - - - - - - - - - - # Miller Informational [Page 75] # RFC 7520 JOSE Cookbook May 2015 # eyJhbGciOiJBMjU2R0NNS1ciLCJraWQiOiIxOGVjMDhlMS1iZmE5LTRkOTUtYj # IwNS0yYjRkZDFkNDMyMWQiLCJ0YWciOiJrZlBkdVZRM1QzSDZ2bmV3dC0ta3N3 # IiwiaXYiOiJLa1lUMEdYXzJqSGxmcU5fIiwiZW5jIjoiQTEyOENCQy1IUzI1Ni # J9 # Figure 145: JWE Protected Header, base64url-encoded # Performing the content encryption operation over the Plaintext # (Figure 72) with the following: # o CEK (Figure 139); # o Initialization Vector (Figure 140); and # o JWE Protected Header (Figure 145) as authenticated data # produces the following: # o Ciphertext from Figure 146. # o Authentication Tag from Figure 147. # Jf5p9-ZhJlJy_IQ_byKFmI0Ro7w7G1QiaZpI8OaiVgD8EqoDZHyFKFBupS8iaE # eVIgMqWmsuJKuoVgzR3YfzoMd3GxEm3VxNhzWyWtZKX0gxKdy6HgLvqoGNbZCz # LjqcpDiF8q2_62EVAbr2uSc2oaxFmFuIQHLcqAHxy51449xkjZ7ewzZaGV3eFq # hpco8o4DijXaG5_7kp3h2cajRfDgymuxUbWgLqaeNQaJtvJmSMFuEOSAzw9Hde # b6yhdTynCRmu-kqtO5Dec4lT2OMZKpnxc_F1_4yDJFcqb5CiDSmA-psB2k0Jtj # xAj4UPI61oONK7zzFIu4gBfjJCndsZfdvG7h8wGjV98QhrKEnR7xKZ3KCr0_qR # 1B-gxpNk3xWU # Figure 146: Ciphertext, base64url-encoded # DKW7jrb4WaRSNfbXVPlT5g # Figure 147: Authentication Tag, base64url-encoded - - - - - - - - - - - - - - # Miller Informational [Page 76] # RFC 7520 JOSE Cookbook May 2015 # 5.7.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 145) # o Encrypted Key (Figure 142) # o Initialization Vector (Figure 140) # o Ciphertext (Figure 146) # o Authentication Tag (Figure 147) # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJBMjU2R0NNS1ciLCJraWQiOiIxOGVjMDhlMS1iZmE5LTRkOTUtYj # IwNS0yYjRkZDFkNDMyMWQiLCJ0YWciOiJrZlBkdVZRM1QzSDZ2bmV3dC0ta3N3 # IiwiaXYiOiJLa1lUMEdYXzJqSGxmcU5fIiwiZW5jIjoiQTEyOENCQy1IUzI1Ni # J9 # . # lJf3HbOApxMEBkCMOoTnnABxs_CvTWUmZQ2ElLvYNok # . # gz6NjyEFNm_vm8Gj6FwoFQ # . # Jf5p9-ZhJlJy_IQ_byKFmI0Ro7w7G1QiaZpI8OaiVgD8EqoDZHyFKFBupS8iaE # eVIgMqWmsuJKuoVgzR3YfzoMd3GxEm3VxNhzWyWtZKX0gxKdy6HgLvqoGNbZCz # LjqcpDiF8q2_62EVAbr2uSc2oaxFmFuIQHLcqAHxy51449xkjZ7ewzZaGV3eFq # hpco8o4DijXaG5_7kp3h2cajRfDgymuxUbWgLqaeNQaJtvJmSMFuEOSAzw9Hde # b6yhdTynCRmu-kqtO5Dec4lT2OMZKpnxc_F1_4yDJFcqb5CiDSmA-psB2k0Jtj # xAj4UPI61oONK7zzFIu4gBfjJCndsZfdvG7h8wGjV98QhrKEnR7xKZ3KCr0_qR # 1B-gxpNk3xWU # . # DKW7jrb4WaRSNfbXVPlT5g # Figure 148: JWE Compact Serialization - - - - - - - - - - - - - # Miller Informational [Page 77] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "lJf3HbOApxMEBkCMOoTnnABxs_CvTWUmZQ2ElL # vYNok" # } # ], # "protected": "eyJhbGciOiJBMjU2R0NNS1ciLCJraWQiOiIxOGVjMDhlMS # 1iZmE5LTRkOTUtYjIwNS0yYjRkZDFkNDMyMWQiLCJ0YWciOiJrZlBkdV # ZRM1QzSDZ2bmV3dC0ta3N3IiwiaXYiOiJLa1lUMEdYXzJqSGxmcU5fIi # wiZW5jIjoiQTEyOENCQy1IUzI1NiJ9", # "iv": "gz6NjyEFNm_vm8Gj6FwoFQ", # "ciphertext": "Jf5p9-ZhJlJy_IQ_byKFmI0Ro7w7G1QiaZpI8OaiVgD8E # qoDZHyFKFBupS8iaEeVIgMqWmsuJKuoVgzR3YfzoMd3GxEm3VxNhzWyW # tZKX0gxKdy6HgLvqoGNbZCzLjqcpDiF8q2_62EVAbr2uSc2oaxFmFuIQ # HLcqAHxy51449xkjZ7ewzZaGV3eFqhpco8o4DijXaG5_7kp3h2cajRfD # gymuxUbWgLqaeNQaJtvJmSMFuEOSAzw9Hdeb6yhdTynCRmu-kqtO5Dec # 4lT2OMZKpnxc_F1_4yDJFcqb5CiDSmA-psB2k0JtjxAj4UPI61oONK7z # zFIu4gBfjJCndsZfdvG7h8wGjV98QhrKEnR7xKZ3KCr0_qR1B-gxpNk3 # xWU", # "tag": "DKW7jrb4WaRSNfbXVPlT5g" # } # Figure 149: General JWE JSON Serialization - - - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 78] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJBMjU2R0NNS1ciLCJpdiI6IktrWVQwR1hfMm # pIbGZxTl8iLCJraWQiOiIxOGVjMDhlMS1iZmE5LTRkOTUtYjIwNS0yYj # RkZDFkNDMyMWQiLCJ0YWciOiJrZlBkdVZRM1QzSDZ2bmV3dC0ta3N3Ii # wiZW5jIjoiQTEyOENCQy1IUzI1NiJ9", # "encrypted_key": "lJf3HbOApxMEBkCMOoTnnABxs_CvTWUmZQ2ElLvYNo # k", # "iv": "gz6NjyEFNm_vm8Gj6FwoFQ", # "ciphertext": "Jf5p9-ZhJlJy_IQ_byKFmI0Ro7w7G1QiaZpI8OaiVgD8E # qoDZHyFKFBupS8iaEeVIgMqWmsuJKuoVgzR3YfzoMd3GxEm3VxNhzWyW # tZKX0gxKdy6HgLvqoGNbZCzLjqcpDiF8q2_62EVAbr2uSc2oaxFmFuIQ # HLcqAHxy51449xkjZ7ewzZaGV3eFqhpco8o4DijXaG5_7kp3h2cajRfD # gymuxUbWgLqaeNQaJtvJmSMFuEOSAzw9Hdeb6yhdTynCRmu-kqtO5Dec # 4lT2OMZKpnxc_F1_4yDJFcqb5CiDSmA-psB2k0JtjxAj4UPI61oONK7z # zFIu4gBfjJCndsZfdvG7h8wGjV98QhrKEnR7xKZ3KCr0_qR1B-gxpNk3 # xWU", # "tag": "NvBveHr_vonkvflfnUrmBQ" # } # Figure 150: Flattened JWE JSON Serialization # 5.8. Key Wrap Using AES-KeyWrap with AES-GCM # The following example illustrates content encryption using the # "A128KW" (AES-128-KeyWrap) key encryption algorithm and the "A128GCM" # (AES-128-GCM) content encryption algorithm. # Note that whitespace is added for readability as described in # Section 1.1. # 5.8.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o AES symmetric key; this example uses the key from Figure 151. # o "alg" parameter of "A128KW". # o "enc" parameter of "A128GCM". - - - - - - # Miller Informational [Page 79] # RFC 7520 JOSE Cookbook May 2015 # { # "kty": "oct", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "use": "enc", # "alg": "A128KW", # "k": "GZy6sIZ6wl9NJOKB-jnmVQ" # } # Figure 151: AES 128-Bit Key # 5.8.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key; this example uses # the key from Figure 152. # o Initialization Vector; this example uses the Initialization Vector # from Figure 153. # aY5_Ghmk9KxWPBLu_glx1w # Figure 152: Content Encryption Key, base64url-encoded # Qx0pmsDa8KnJc9Jo # Figure 153: Initialization Vector, base64url-encoded # 5.8.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 152) # with the AES symmetric key (Figure 151) produces the following # Encrypted Key: # CBI6oDw8MydIx1IBntf_lQcw2MmJKIQx # Figure 154: Encrypted Key, base64url-encoded # 5.8.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 155, encoded to base64url [RFC4648] as Figure 156. - - - - - # Miller Informational [Page 80] # RFC 7520 JOSE Cookbook May 2015 # { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM" # } # Figure 155: JWE Protected Header JSON # eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC # 04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn0 # Figure 156: JWE Protected Header, base64url-encoded # Performing the content encryption over the Plaintext (Figure 72) with # the following: # o CEK (Figure 152); # o Initialization Vector (Figure 153); and # o JWE Protected Header (Figure 156) as authenticated data # produces the following: # o Ciphertext from Figure 157. # o Authentication Tag from Figure 158. # AwliP-KmWgsZ37BvzCefNen6VTbRK3QMA4TkvRkH0tP1bTdhtFJgJxeVmJkLD6 # 1A1hnWGetdg11c9ADsnWgL56NyxwSYjU1ZEHcGkd3EkU0vjHi9gTlb90qSYFfe # F0LwkcTtjbYKCsiNJQkcIp1yeM03OmuiYSoYJVSpf7ej6zaYcMv3WwdxDFl8RE # wOhNImk2Xld2JXq6BR53TSFkyT7PwVLuq-1GwtGHlQeg7gDT6xW0JqHDPn_H-p # uQsmthc9Zg0ojmJfqqFvETUxLAF-KjcBTS5dNy6egwkYtOt8EIHK-oEsKYtZRa # a8Z7MOZ7UGxGIMvEmxrGCPeJa14slv2-gaqK0kEThkaSqdYw0FkQZF # Figure 157: Ciphertext, base64url-encoded # ER7MWJZ1FBI_NKvn7Zb1Lw # Figure 158: Authentication Tag, base64url-encoded - - - - - - - - - # Miller Informational [Page 81] # RFC 7520 JOSE Cookbook May 2015 # 5.8.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 156) # o Encrypted Key (Figure 154) # o Initialization Vector (Figure 153) # o Ciphertext (Figure 157) # o Authentication Tag (Figure 158) # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC # 04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn0 # . # CBI6oDw8MydIx1IBntf_lQcw2MmJKIQx # . # Qx0pmsDa8KnJc9Jo # . # AwliP-KmWgsZ37BvzCefNen6VTbRK3QMA4TkvRkH0tP1bTdhtFJgJxeVmJkLD6 # 1A1hnWGetdg11c9ADsnWgL56NyxwSYjU1ZEHcGkd3EkU0vjHi9gTlb90qSYFfe # F0LwkcTtjbYKCsiNJQkcIp1yeM03OmuiYSoYJVSpf7ej6zaYcMv3WwdxDFl8RE # wOhNImk2Xld2JXq6BR53TSFkyT7PwVLuq-1GwtGHlQeg7gDT6xW0JqHDPn_H-p # uQsmthc9Zg0ojmJfqqFvETUxLAF-KjcBTS5dNy6egwkYtOt8EIHK-oEsKYtZRa # a8Z7MOZ7UGxGIMvEmxrGCPeJa14slv2-gaqK0kEThkaSqdYw0FkQZF # . # ER7MWJZ1FBI_NKvn7Zb1Lw # Figure 159: JWE Compact Serialization - - - - - - - - - - - - - - - - # Miller Informational [Page 82] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "CBI6oDw8MydIx1IBntf_lQcw2MmJKIQx" # } # ], # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn # 0", # "iv": "Qx0pmsDa8KnJc9Jo", # "ciphertext": "AwliP-KmWgsZ37BvzCefNen6VTbRK3QMA4TkvRkH0tP1b # TdhtFJgJxeVmJkLD61A1hnWGetdg11c9ADsnWgL56NyxwSYjU1ZEHcGk # d3EkU0vjHi9gTlb90qSYFfeF0LwkcTtjbYKCsiNJQkcIp1yeM03OmuiY # SoYJVSpf7ej6zaYcMv3WwdxDFl8REwOhNImk2Xld2JXq6BR53TSFkyT7 # PwVLuq-1GwtGHlQeg7gDT6xW0JqHDPn_H-puQsmthc9Zg0ojmJfqqFvE # TUxLAF-KjcBTS5dNy6egwkYtOt8EIHK-oEsKYtZRaa8Z7MOZ7UGxGIMv # EmxrGCPeJa14slv2-gaqK0kEThkaSqdYw0FkQZF", # "tag": "ER7MWJZ1FBI_NKvn7Zb1Lw" # } # Figure 160: General JWE JSON Serialization # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn # 0", # "encrypted_key": "CBI6oDw8MydIx1IBntf_lQcw2MmJKIQx", # "iv": "Qx0pmsDa8KnJc9Jo", # "ciphertext": "AwliP-KmWgsZ37BvzCefNen6VTbRK3QMA4TkvRkH0tP1b # TdhtFJgJxeVmJkLD61A1hnWGetdg11c9ADsnWgL56NyxwSYjU1ZEHcGk # d3EkU0vjHi9gTlb90qSYFfeF0LwkcTtjbYKCsiNJQkcIp1yeM03OmuiY # SoYJVSpf7ej6zaYcMv3WwdxDFl8REwOhNImk2Xld2JXq6BR53TSFkyT7 # PwVLuq-1GwtGHlQeg7gDT6xW0JqHDPn_H-puQsmthc9Zg0ojmJfqqFvE # TUxLAF-KjcBTS5dNy6egwkYtOt8EIHK-oEsKYtZRaa8Z7MOZ7UGxGIMv # EmxrGCPeJa14slv2-gaqK0kEThkaSqdYw0FkQZF", # "tag": "ER7MWJZ1FBI_NKvn7Zb1Lw" # } # Figure 161: Flattened JWE JSON Serialization - - - - - - # Miller Informational [Page 83] # RFC 7520 JOSE Cookbook May 2015 # 5.9. Compressed Content # This example illustrates encrypting content that is first compressed. # It reuses the AES symmetric key, key encryption algorithm, and # content encryption algorithm from Section 5.8. # Note that whitespace is added for readability as described in # Section 1.1. # 5.9.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o Recipient encryption key; this example uses the key from # Figure 151. # o Key encryption algorithm; this example uses "A128KW". # o Content encryption algorithm; this example uses "A128GCM". # o "zip" parameter of "DEF". # 5.9.2. Generated Factors # The following are generated before encrypting: # o Compressed Plaintext from the original Plaintext content; # compressing Figure 72 using the DEFLATE [RFC1951] algorithm # produces the compressed Plaintext from Figure 162. # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 163. # o Initialization Vector; this example uses the Initialization Vector # from Figure 164. # bY_BDcIwDEVX-QNU3QEOrIA4pqlDokYxchxVvbEDGzIJbioOSJwc-f___HPjBu # 8KVFpVtAplVE1-wZo0YjNZo3C7R5v72pV5f5X382VWjYQpqZKAyjziZOr2B7kQ # PSy6oZIXUnDYbVKN4jNXi2u0yB7t1qSHTjmMODf9QgvrDzfTIQXnyQRuUya4zI # WG3vTOdir0v7BRHFYWq3k1k1A_gSDJqtcBF-GZxw8 # Figure 162: Compressed Plaintext, base64url-encoded - - - - - # Miller Informational [Page 84] # RFC 7520 JOSE Cookbook May 2015 # hC-MpLZSuwWv8sexS6ydfw # Figure 163: Content Encryption Key, base64url-encoded # p9pUq6XHY0jfEZIl # Figure 164: Initialization Vector, base64url-encoded # 5.9.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 163) # with the AES symmetric key (Figure 151) produces the following # Encrypted Key: # 5vUT2WOtQxKWcekM_IzVQwkGgzlFDwPi # Figure 165: Encrypted Key, base64url-encoded # 5.9.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 166, encoded to base64url [RFC4648] as Figure 167. # { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM", # "zip": "DEF" # } # Figure 166: JWE Protected Header JSON # eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC # 04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIiwiemlwIjoiREVGIn0 # Figure 167: JWE Protected Header, base64url-encoded - - - - - - - - - - - # Miller Informational [Page 85] # RFC 7520 JOSE Cookbook May 2015 # Performing the content encryption operation over the compressed # Plaintext (Figure 162, encoded as an octet string) with the # following: # o CEK (Figure 163); # o Initialization Vector (Figure 164); and # o JWE Protected Header (Figure 167) as authenticated data # produces the following: # o Ciphertext from Figure 168. # o Authentication Tag from Figure 169. # HbDtOsdai1oYziSx25KEeTxmwnh8L8jKMFNc1k3zmMI6VB8hry57tDZ61jXyez # SPt0fdLVfe6Jf5y5-JaCap_JQBcb5opbmT60uWGml8blyiMQmOn9J--XhhlYg0 # m-BHaqfDO5iTOWxPxFMUedx7WCy8mxgDHj0aBMG6152PsM-w5E_o2B3jDbrYBK # hpYA7qi3AyijnCJ7BP9rr3U8kxExCpG3mK420TjOw # Figure 168: Ciphertext, base64url-encoded # VILuUwuIxaLVmh5X-T7kmA # Figure 169: Authentication Tag, base64url-encoded # 5.9.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 167) # o Encrypted Key (Figure 165) # o Initialization Vector (Figure 164) # o Ciphertext (Figure 168) # o Authentication Tag (Figure 169) - - - - - - - - - # Miller Informational [Page 86] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC # 04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIiwiemlwIjoiREVGIn0 # . # 5vUT2WOtQxKWcekM_IzVQwkGgzlFDwPi # . # p9pUq6XHY0jfEZIl # . # HbDtOsdai1oYziSx25KEeTxmwnh8L8jKMFNc1k3zmMI6VB8hry57tDZ61jXyez # SPt0fdLVfe6Jf5y5-JaCap_JQBcb5opbmT60uWGml8blyiMQmOn9J--XhhlYg0 # m-BHaqfDO5iTOWxPxFMUedx7WCy8mxgDHj0aBMG6152PsM-w5E_o2B3jDbrYBK # hpYA7qi3AyijnCJ7BP9rr3U8kxExCpG3mK420TjOw # . # VILuUwuIxaLVmh5X-T7kmA # Figure 170: JWE Compact Serialization # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "5vUT2WOtQxKWcekM_IzVQwkGgzlFDwPi" # } # ], # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIi # wiemlwIjoiREVGIn0", # "iv": "p9pUq6XHY0jfEZIl", # "ciphertext": "HbDtOsdai1oYziSx25KEeTxmwnh8L8jKMFNc1k3zmMI6V # B8hry57tDZ61jXyezSPt0fdLVfe6Jf5y5-JaCap_JQBcb5opbmT60uWG # ml8blyiMQmOn9J--XhhlYg0m-BHaqfDO5iTOWxPxFMUedx7WCy8mxgDH # j0aBMG6152PsM-w5E_o2B3jDbrYBKhpYA7qi3AyijnCJ7BP9rr3U8kxE # xCpG3mK420TjOw", # "tag": "VILuUwuIxaLVmh5X-T7kmA" # } # Figure 171: General JWE JSON Serialization - - - - - - - - - - # Miller Informational [Page 87] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIi # wiemlwIjoiREVGIn0", # "encrypted_key": "5vUT2WOtQxKWcekM_IzVQwkGgzlFDwPi", # "iv": "p9pUq6XHY0jfEZIl", # "ciphertext": "HbDtOsdai1oYziSx25KEeTxmwnh8L8jKMFNc1k3zmMI6V # B8hry57tDZ61jXyezSPt0fdLVfe6Jf5y5-JaCap_JQBcb5opbmT60uWG # ml8blyiMQmOn9J--XhhlYg0m-BHaqfDO5iTOWxPxFMUedx7WCy8mxgDH # j0aBMG6152PsM-w5E_o2B3jDbrYBKhpYA7qi3AyijnCJ7BP9rr3U8kxE # xCpG3mK420TjOw", # "tag": "VILuUwuIxaLVmh5X-T7kmA" # } # Figure 172: Flattened JWE JSON Serialization # 5.10. Including Additional Authenticated Data # This example illustrates encrypting content that includes additional # authenticated data. As this example includes an additional top-level # property not present in the JWE Compact Serialization, only the # flattened JWE JSON Serialization and general JWE JSON Serialization # are possible. # Note that whitespace is added for readability as described in # Section 1.1. # 5.10.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o Recipient encryption key; this example uses the key from # Figure 151. # o Key encryption algorithm; this example uses "A128KW". # o Content encryption algorithm; this example uses "A128GCM". # o Additional Authenticated Data; this example uses a vCard [RFC7095] # from Figure 173, serialized to UTF-8. - - - - - # Miller Informational [Page 88] # RFC 7520 JOSE Cookbook May 2015 # [ # "vcard", # [ # [ "version", {}, "text", "4.0" ], # [ "fn", {}, "text", "Meriadoc Brandybuck" ], # [ "n", {}, # "text", [ # "Brandybuck", "Meriadoc", "Mr.", "" # ] # ], # [ "bday", {}, "text", "TA 2982" ], # [ "gender", {}, "text", "M" ] # ] # ] # Figure 173: Additional Authenticated Data, in JSON Format # NOTE: Whitespace between JSON values was added for readability. # 5.10.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 174. # o Initialization Vector; this example uses the Initialization Vector # from Figure 175. # o Encoded Additional Authenticated Data (AAD); this example uses the # Additional Authenticated Data from Figure 173, encoded to # base64url [RFC4648] as Figure 176. # 75m1ALsYv10pZTKPWrsqdg # Figure 174: Content Encryption Key, base64url-encoded # veCx9ece2orS7c_N # Figure 175: Initialization Vector, base64url-encoded # WyJ2Y2FyZCIsW1sidmVyc2lvbiIse30sInRleHQiLCI0LjAiXSxbImZuIix7fS # widGV4dCIsIk1lcmlhZG9jIEJyYW5keWJ1Y2siXSxbIm4iLHt9LCJ0ZXh0Iixb # IkJyYW5keWJ1Y2siLCJNZXJpYWRvYyIsIk1yLiIsIiJdXSxbImJkYXkiLHt9LC # J0ZXh0IiwiVEEgMjk4MiJdLFsiZ2VuZGVyIix7fSwidGV4dCIsIk0iXV1d # Figure 176: Additional Authenticated Data, base64url-encoded - - # Miller Informational [Page 89] # RFC 7520 JOSE Cookbook May 2015 # 5.10.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 174) # with the AES symmetric key (Figure 151) produces the following # Encrypted Key: # 4YiiQ_ZzH76TaIkJmYfRFgOV9MIpnx4X # Figure 177: Encrypted Key, base64url-encoded # 5.10.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 178, encoded to base64url [RFC4648] as Figure 179. # { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM" # } # Figure 178: JWE Protected Header JSON # eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04MzMyLTQzZDktYTQ2OC # 04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn0 # Figure 179: JWE Protected Header, base64url-encoded # Performing the content encryption operation over the Plaintext with # the following: # o CEK (Figure 174); # o Initialization Vector (Figure 175); and # o Concatenation of the JWE Protected Header (Figure 179), ".", and # the base64url [RFC4648] encoding of Figure 173 as authenticated # data # produces the following: -# o Ciphertext from Figure 180. - -# o Authentication Tag from Figure 181. - - +# o Ciphertext from Figure 180. +# o Authentication Tag from Figure 181. # Miller Informational [Page 90] # RFC 7520 JOSE Cookbook May 2015 # Z_3cbr0k3bVM6N3oSNmHz7Lyf3iPppGf3Pj17wNZqteJ0Ui8p74SchQP8xygM1 # oFRWCNzeIa6s6BcEtp8qEFiqTUEyiNkOWDNoF14T_4NFqF-p2Mx8zkbKxI7oPK # 8KNarFbyxIDvICNqBLba-v3uzXBdB89fzOI-Lv4PjOFAQGHrgv1rjXAmKbgkft # 9cB4WeyZw8MldbBhc-V_KWZslrsLNygon_JJWd_ek6LQn5NRehvApqf9ZrxB4a # q3FXBxOxCys35PhCdaggy2kfUfl2OkwKnWUbgXVD1C6HxLIlqHhCwXDG59weHr # RDQeHyMRoBljoV3X_bUTJDnKBFOod7nLz-cj48JMx3SnCZTpbQAkFV # Figure 180: Ciphertext, base64url-encoded # vOaH_Rajnpy_3hOtqvZHRA # Figure 181: Authentication Tag, base64url-encoded # 5.10.5. Output Results # The following compose the resulting JWE object: # o JWE Protected Header (Figure 179) # o Encrypted Key (Figure 177) # o Initialization Vector (Figure 175) # o Additional Authenticated Data (Figure 176) # o Ciphertext (Figure 180) # o Authentication Tag (Figure 181) # The JWE Compact Serialization is not presented because it does not # support this use case. - - - - - - - - - - - - - - - - - - # Miller Informational [Page 91] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "4YiiQ_ZzH76TaIkJmYfRFgOV9MIpnx4X" # } # ], # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn # 0", # "iv": "veCx9ece2orS7c_N", # "aad": "WyJ2Y2FyZCIsW1sidmVyc2lvbiIse30sInRleHQiLCI0LjAiXSxb # ImZuIix7fSwidGV4dCIsIk1lcmlhZG9jIEJyYW5keWJ1Y2siXSxbIm4i # LHt9LCJ0ZXh0IixbIkJyYW5keWJ1Y2siLCJNZXJpYWRvYyIsIk1yLiIs # IiJdXSxbImJkYXkiLHt9LCJ0ZXh0IiwiVEEgMjk4MiJdLFsiZ2VuZGVy # Iix7fSwidGV4dCIsIk0iXV1d", # "ciphertext": "Z_3cbr0k3bVM6N3oSNmHz7Lyf3iPppGf3Pj17wNZqteJ0 # Ui8p74SchQP8xygM1oFRWCNzeIa6s6BcEtp8qEFiqTUEyiNkOWDNoF14 # T_4NFqF-p2Mx8zkbKxI7oPK8KNarFbyxIDvICNqBLba-v3uzXBdB89fz # OI-Lv4PjOFAQGHrgv1rjXAmKbgkft9cB4WeyZw8MldbBhc-V_KWZslrs # LNygon_JJWd_ek6LQn5NRehvApqf9ZrxB4aq3FXBxOxCys35PhCdaggy # 2kfUfl2OkwKnWUbgXVD1C6HxLIlqHhCwXDG59weHrRDQeHyMRoBljoV3 # X_bUTJDnKBFOod7nLz-cj48JMx3SnCZTpbQAkFV", # "tag": "vOaH_Rajnpy_3hOtqvZHRA" # } # Figure 182: General JWE JSON Serialization - - - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 92] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJhbGciOiJBMTI4S1ciLCJraWQiOiI4MWIyMDk2NS04Mz # MyLTQzZDktYTQ2OC04MjE2MGFkOTFhYzgiLCJlbmMiOiJBMTI4R0NNIn # 0", # "encrypted_key": "4YiiQ_ZzH76TaIkJmYfRFgOV9MIpnx4X", # "aad": "WyJ2Y2FyZCIsW1sidmVyc2lvbiIse30sInRleHQiLCI0LjAiXSxb # ImZuIix7fSwidGV4dCIsIk1lcmlhZG9jIEJyYW5keWJ1Y2siXSxbIm4i # LHt9LCJ0ZXh0IixbIkJyYW5keWJ1Y2siLCJNZXJpYWRvYyIsIk1yLiIs # IiJdXSxbImJkYXkiLHt9LCJ0ZXh0IiwiVEEgMjk4MiJdLFsiZ2VuZGVy # Iix7fSwidGV4dCIsIk0iXV1d", # "iv": "veCx9ece2orS7c_N", # "ciphertext": "Z_3cbr0k3bVM6N3oSNmHz7Lyf3iPppGf3Pj17wNZqteJ0 # Ui8p74SchQP8xygM1oFRWCNzeIa6s6BcEtp8qEFiqTUEyiNkOWDNoF14 # T_4NFqF-p2Mx8zkbKxI7oPK8KNarFbyxIDvICNqBLba-v3uzXBdB89fz # OI-Lv4PjOFAQGHrgv1rjXAmKbgkft9cB4WeyZw8MldbBhc-V_KWZslrs # LNygon_JJWd_ek6LQn5NRehvApqf9ZrxB4aq3FXBxOxCys35PhCdaggy # 2kfUfl2OkwKnWUbgXVD1C6HxLIlqHhCwXDG59weHrRDQeHyMRoBljoV3 # X_bUTJDnKBFOod7nLz-cj48JMx3SnCZTpbQAkFV", # "tag": "vOaH_Rajnpy_3hOtqvZHRA" # } # Figure 183: Flattened JWE JSON Serialization # 5.11. Protecting Specific Header Fields # This example illustrates encrypting content where only certain JOSE # Header Parameters are protected. As this example includes parameters # in the JWE Shared Unprotected Header, only the general JWE JSON # Serialization and flattened JWE JSON Serialization are possible. # Note that whitespace is added for readability as described in # Section 1.1. # 5.11.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o Recipient encryption key; this example uses the key from # Figure 151. # o Key encryption algorithm; this example uses "A128KW". # o Content encryption algorithm; this example uses "A128GCM". - - # Miller Informational [Page 93] # RFC 7520 JOSE Cookbook May 2015 # 5.11.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 184. # o Initialization Vector; this example uses the Initialization Vector # from Figure 185. # WDgEptBmQs9ouUvArz6x6g # Figure 184: Content Encryption Key, base64url-encoded # WgEJsDS9bkoXQ3nR # Figure 185: Initialization Vector, base64url-encoded # 5.11.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 184) # with the AES symmetric key (Figure 151) produces the following # Encrypted Key: # jJIcM9J-hbx3wnqhf5FlkEYos0sHsF0H # Figure 186: Encrypted Key, base64url-encoded # 5.11.4. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 187, encoded to base64url [RFC4648] as Figure 188. # { # "enc": "A128GCM" # } # Figure 187: JWE Protected Header JSON # eyJlbmMiOiJBMTI4R0NNIn0 # Figure 188: JWE Protected Header, base64url-encoded - - - - - # Miller Informational [Page 94] # RFC 7520 JOSE Cookbook May 2015 # Performing the content encryption operation over the Plaintext with # the following: # o CEK (Figure 184); # o Initialization Vector (Figure 185); and # o JWE Protected Header (Figure 188) as authenticated data # produces the following: # o Ciphertext from Figure 189. # o Authentication Tag from Figure 190. # lIbCyRmRJxnB2yLQOTqjCDKV3H30ossOw3uD9DPsqLL2DM3swKkjOwQyZtWsFL # YMj5YeLht_StAn21tHmQJuuNt64T8D4t6C7kC9OCCJ1IHAolUv4MyOt80MoPb8 # fZYbNKqplzYJgIL58g8N2v46OgyG637d6uuKPwhAnTGm_zWhqc_srOvgiLkzyF # XPq1hBAURbc3-8BqeRb48iR1-_5g5UjWVD3lgiLCN_P7AW8mIiFvUNXBPJK3nO # WL4teUPS8yHLbWeL83olU4UAgL48x-8dDkH23JykibVSQju-f7e-1xreHWXzWL # Hs1NqBbre0dEwK3HX_xM0LjUz77Krppgegoutpf5qaKg3l-_xMINmf # Figure 189: Ciphertext, base64url-encoded # fNYLqpUe84KD45lvDiaBAQ # Figure 190: Authentication Tag, base64url-encoded # 5.11.5. Output Results # The following compose the resulting JWE object: # o JWE Shared Unprotected Header (Figure 191) # o JWE Protected Header (Figure 188) # o Encrypted Key (Figure 186) # o Initialization Vector (Figure 185) # o Ciphertext (Figure 189) # o Authentication Tag (Figure 190) # The JWE Compact Serialization is not presented because it does not # support this use case. - - - # Miller Informational [Page 95] # RFC 7520 JOSE Cookbook May 2015 # The following JWE Shared Unprotected Header is generated before # assembling the output results: # { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8" # } # Figure 191: JWE Shared Unprotected Header JSON # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "jJIcM9J-hbx3wnqhf5FlkEYos0sHsF0H" # } # ], # "unprotected": { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8" # }, # "protected": "eyJlbmMiOiJBMTI4R0NNIn0", # "iv": "WgEJsDS9bkoXQ3nR", # "ciphertext": "lIbCyRmRJxnB2yLQOTqjCDKV3H30ossOw3uD9DPsqLL2D # M3swKkjOwQyZtWsFLYMj5YeLht_StAn21tHmQJuuNt64T8D4t6C7kC9O # CCJ1IHAolUv4MyOt80MoPb8fZYbNKqplzYJgIL58g8N2v46OgyG637d6 # uuKPwhAnTGm_zWhqc_srOvgiLkzyFXPq1hBAURbc3-8BqeRb48iR1-_5 # g5UjWVD3lgiLCN_P7AW8mIiFvUNXBPJK3nOWL4teUPS8yHLbWeL83olU # 4UAgL48x-8dDkH23JykibVSQju-f7e-1xreHWXzWLHs1NqBbre0dEwK3 # HX_xM0LjUz77Krppgegoutpf5qaKg3l-_xMINmf", # "tag": "fNYLqpUe84KD45lvDiaBAQ" # } # Figure 192: General JWE JSON Serialization - - - - - - - - - - - - - - # Miller Informational [Page 96] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "protected": "eyJlbmMiOiJBMTI4R0NNIn0", # "unprotected": { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8" # }, # "encrypted_key": "jJIcM9J-hbx3wnqhf5FlkEYos0sHsF0H", # "iv": "WgEJsDS9bkoXQ3nR", # "ciphertext": "lIbCyRmRJxnB2yLQOTqjCDKV3H30ossOw3uD9DPsqLL2D # M3swKkjOwQyZtWsFLYMj5YeLht_StAn21tHmQJuuNt64T8D4t6C7kC9O # CCJ1IHAolUv4MyOt80MoPb8fZYbNKqplzYJgIL58g8N2v46OgyG637d6 # uuKPwhAnTGm_zWhqc_srOvgiLkzyFXPq1hBAURbc3-8BqeRb48iR1-_5 # g5UjWVD3lgiLCN_P7AW8mIiFvUNXBPJK3nOWL4teUPS8yHLbWeL83olU # 4UAgL48x-8dDkH23JykibVSQju-f7e-1xreHWXzWLHs1NqBbre0dEwK3 # HX_xM0LjUz77Krppgegoutpf5qaKg3l-_xMINmf", # "tag": "fNYLqpUe84KD45lvDiaBAQ" # } # Figure 193: Flattened JWE JSON Serialization # 5.12. Protecting Content Only # This example illustrates encrypting content where none of the JOSE # header parameters are protected. As this example includes parameters # only in the JWE Shared Unprotected Header, only the flattened JWE # JSON Serialization and general JWE JSON Serialization are possible. # Note that whitespace is added for readability as described in # Section 1.1. # 5.12.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 72. # o Recipient encryption key; this example uses the key from # Figure 151. # o Key encryption algorithm; this example uses "A128KW". # o Content encryption algorithm; this example uses "A128GCM". - - - - - # Miller Informational [Page 97] # RFC 7520 JOSE Cookbook May 2015 # 5.12.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key; this example the # key from Figure 194. # o Initialization Vector; this example uses the Initialization Vector # from Figure 195. # KBooAFl30QPV3vkcZlXnzQ # Figure 194: Content Encryption Key, base64url-encoded # YihBoVOGsR1l7jCD # Figure 195: Initialization Vector, base64url-encoded # 5.12.3. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 194) # with the AES symmetric key (Figure 151) produces the following # Encrypted Key: # 244YHfO_W7RMpQW81UjQrZcq5LSyqiPv # Figure 196: Encrypted Key, base64url-encoded # 5.12.4. Encrypting the Content # Performing the content encryption operation over the Plaintext # (Figure 72) using the following: # o CEK (Figure 194); # o Initialization Vector (Figure 195); and # o Empty string as authenticated data # produces the following: # o Ciphertext from Figure 197. # o Authentication Tag from Figure 198. - - - - - # Miller Informational [Page 98] # RFC 7520 JOSE Cookbook May 2015 # qtPIMMaOBRgASL10dNQhOa7Gqrk7Eal1vwht7R4TT1uq-arsVCPaIeFwQfzrSS # 6oEUWbBtxEasE0vC6r7sphyVziMCVJEuRJyoAHFSP3eqQPb4Ic1SDSqyXjw_L3 # svybhHYUGyQuTmUQEDjgjJfBOifwHIsDsRPeBz1NomqeifVPq5GTCWFo5k_MNI # QURR2Wj0AHC2k7JZfu2iWjUHLF8ExFZLZ4nlmsvJu_mvifMYiikfNfsZAudISO # a6O73yPZtL04k_1FI7WDfrb2w7OqKLWDXzlpcxohPVOLQwpA3mFNRKdY-bQz4Z # 4KX9lfz1cne31N4-8BKmojpw-OdQjKdLOGkC445Fb_K1tlDQXw2sBF # Figure 197: Ciphertext, base64url-encoded # e2m0Vm7JvjK2VpCKXS-kyg # Figure 198: Authentication Tag, base64url-encoded # 5.12.5. Output Results # The JWE Compact Serialization is not presented because it does not # support this use case. # The following JWE Shared Unprotected Header is generated before # assembling the output results: # { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM" # } # Figure 199: JWE Shared Unprotected Header JSON # The following compose the resulting JWE object: # o JWE Shared Unprotected Header (Figure 199) # o Encrypted Key (Figure 196) # o Initialization Vector (Figure 195) # o Ciphertext (Figure 197) # o Authentication Tag (Figure 198) - - - - - - - - - # Miller Informational [Page 99] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "244YHfO_W7RMpQW81UjQrZcq5LSyqiPv" # } # ], # "unprotected": { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM" # }, # "iv": "YihBoVOGsR1l7jCD", # "ciphertext": "qtPIMMaOBRgASL10dNQhOa7Gqrk7Eal1vwht7R4TT1uq- # arsVCPaIeFwQfzrSS6oEUWbBtxEasE0vC6r7sphyVziMCVJEuRJyoAHF # SP3eqQPb4Ic1SDSqyXjw_L3svybhHYUGyQuTmUQEDjgjJfBOifwHIsDs # RPeBz1NomqeifVPq5GTCWFo5k_MNIQURR2Wj0AHC2k7JZfu2iWjUHLF8 # ExFZLZ4nlmsvJu_mvifMYiikfNfsZAudISOa6O73yPZtL04k_1FI7WDf # rb2w7OqKLWDXzlpcxohPVOLQwpA3mFNRKdY-bQz4Z4KX9lfz1cne31N4 # -8BKmojpw-OdQjKdLOGkC445Fb_K1tlDQXw2sBF", # "tag": "e2m0Vm7JvjK2VpCKXS-kyg" # } # Figure 200: General JWE JSON Serialization # The resulting JWE object using the flattened JWE JSON Serialization: # { # "unprotected": { # "alg": "A128KW", # "kid": "81b20965-8332-43d9-a468-82160ad91ac8", # "enc": "A128GCM" # }, # "encrypted_key": "244YHfO_W7RMpQW81UjQrZcq5LSyqiPv", # "iv": "YihBoVOGsR1l7jCD", # "ciphertext": "qtPIMMaOBRgASL10dNQhOa7Gqrk7Eal1vwht7R4TT1uq- # arsVCPaIeFwQfzrSS6oEUWbBtxEasE0vC6r7sphyVziMCVJEuRJyoAHF # SP3eqQPb4Ic1SDSqyXjw_L3svybhHYUGyQuTmUQEDjgjJfBOifwHIsDs # RPeBz1NomqeifVPq5GTCWFo5k_MNIQURR2Wj0AHC2k7JZfu2iWjUHLF8 # ExFZLZ4nlmsvJu_mvifMYiikfNfsZAudISOa6O73yPZtL04k_1FI7WDf # rb2w7OqKLWDXzlpcxohPVOLQwpA3mFNRKdY-bQz4Z4KX9lfz1cne31N4 # -8BKmojpw-OdQjKdLOGkC445Fb_K1tlDQXw2sBF", # "tag": "e2m0Vm7JvjK2VpCKXS-kyg" # } # Figure 201: Flattened JWE JSON Serialization - - # Miller Informational [Page 100] # RFC 7520 JOSE Cookbook May 2015 # 5.13. Encrypting to Multiple Recipients # This example illustrates encryption content for multiple recipients. # As this example has multiple recipients, only the general JWE JSON # Serialization is possible. # Note that RSAES-PKCS1-v1_5 uses random data to generate the # ciphertext; it might not be possible to exactly replicate the results # in this section. # Note that whitespace is added for readability as described in # Section 1.1. # 5.13.1. Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the Plaintext from Figure 72. # o Recipient keys; this example uses the following: # * The RSA public key from Figure 73 for the first recipient. # * The EC public key from Figure 108 for the second recipient. # * The AES symmetric key from Figure 138 for the third recipient. # o Key encryption algorithms; this example uses the following: # * "RSA1_5" for the first recipient. # * "ECDH-ES+A256KW" for the second recipient. # * "A256GCMKW" for the third recipient. # o Content encryption algorithm; this example uses "A128CBC-HS256". # 5.13.2. Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 202. # o Initialization Vector; this example uses the Initialization Vector # from Figure 203. - - - # Miller Informational [Page 101] # RFC 7520 JOSE Cookbook May 2015 # zXayeJ4gvm8NJr3IUInyokTUO-LbQNKEhe_zWlYbdpQ # Figure 202: Content Encryption Key, base64url-encoded # VgEIHY20EnzUtZFl2RpB1g # Figure 203: Initialization Vector, base64url-encoded # 5.13.3. Encrypting the Key to the First Recipient # Performing the "RSA1_5" key encryption operation over the CEK # (Figure 202) with the first recipient's RSA key (Figure 73) produces # the following Encrypted Key: # dYOD28kab0Vvf4ODgxVAJXgHcSZICSOp8M51zjwj4w6Y5G4XJQsNNIBiqyvUUA # OcpL7S7-cFe7Pio7gV_Q06WmCSa-vhW6me4bWrBf7cHwEQJdXihidAYWVajJIa # KMXMvFRMV6iDlRr076DFthg2_AV0_tSiV6xSEIFqt1xnYPpmP91tc5WJDOGb-w # qjw0-b-S1laS11QVbuP78dQ7Fa0zAVzzjHX-xvyM2wxj_otxr9clN1LnZMbeYS # rRicJK5xodvWgkpIdkMHo4LvdhRRvzoKzlic89jFWPlnBq_V4n5trGuExtp_-d # bHcGlihqc_wGgho9fLMK8JOArYLcMDNQ # Figure 204: Recipient #1 Encrypted Key, base64url-encoded # The following is generated after encrypting the CEK for the first # recipient: # o Recipient JWE Unprotected Header from Figure 205. # { # "alg": "RSA1_5", # "kid": "frodo.baggins@hobbiton.example" # } # Figure 205: Recipient #1 JWE Per-Recipient Unprotected Header JSON - - - - - - - - - - - - - - - # Miller Informational [Page 102] # RFC 7520 JOSE Cookbook May 2015 # The following is the assembled first recipient JSON: # { # "encrypted_key": "dYOD28kab0Vvf4ODgxVAJXgHcSZICSOp8M51zjwj4w # 6Y5G4XJQsNNIBiqyvUUAOcpL7S7-cFe7Pio7gV_Q06WmCSa-vhW6me4b # WrBf7cHwEQJdXihidAYWVajJIaKMXMvFRMV6iDlRr076DFthg2_AV0_t # SiV6xSEIFqt1xnYPpmP91tc5WJDOGb-wqjw0-b-S1laS11QVbuP78dQ7 # Fa0zAVzzjHX-xvyM2wxj_otxr9clN1LnZMbeYSrRicJK5xodvWgkpIdk # MHo4LvdhRRvzoKzlic89jFWPlnBq_V4n5trGuExtp_-dbHcGlihqc_wG # gho9fLMK8JOArYLcMDNQ", # "header": { # "alg": "RSA1_5", # "kid": "frodo.baggins@hobbiton.example" # } # } # Figure 206: Recipient #1 JSON # 5.13.4. Encrypting the Key to the Second Recipient # The following is generated before encrypting the CEK for the second # recipient: # o Ephemeral EC private key on the same curve as the EC public key; # this example uses the private key from Figure 207. # { # "kty": "EC", # "crv": "P-384", # "x": "Uzdvk3pi5wKCRc1izp5_r0OjeqT-I68i8g2b8mva8diRhsE2xAn2Dt # MRb25Ma2CX", # "y": "VDrRyFJh-Kwd1EjAgmj5Eo-CTHAZ53MC7PjjpLioy3ylEjI1pOMbw9 # 1fzZ84pbfm", # "d": "1DKHfTv-PiifVw2VBHM_ZiVcwOMxkOyANS_lQHJcrDxVY3jhVCvZPw # MxJKIE793C" # } # Figure 207: Ephemeral Private Key for Recipient #2, in JWK Format - - - - - - - - - - - # Miller Informational [Page 103] # RFC 7520 JOSE Cookbook May 2015 # Performing the "ECDH-ES+A256KW" key encryption operation over the CEK # (Figure 202) with the following: # o Static Elliptic Curve public key (Figure 108). # o Ephemeral Elliptic Curve private key (Figure 207). # produces the following Encrypted Key: # ExInT0io9BqBMYF6-maw5tZlgoZXThD1zWKsHixJuw_elY4gSSId_w # Figure 208: Recipient #2 Encrypted Key, base64url-encoded # The following is generated after encrypting the CEK for the second # recipient: # o Recipient JWE Unprotected Header from Figure 209. # { # "alg": "ECDH-ES+A256KW", # "kid": "peregrin.took@tuckborough.example", # "epk": { # "kty": "EC", # "crv": "P-384", # "x": "Uzdvk3pi5wKCRc1izp5_r0OjeqT-I68i8g2b8mva8diRhsE2xAn2 # DtMRb25Ma2CX", # "y": "VDrRyFJh-Kwd1EjAgmj5Eo-CTHAZ53MC7PjjpLioy3ylEjI1pOMb # w91fzZ84pbfm" # } # } # Figure 209: Recipient #2 JWE Per-Recipient Unprotected Header JSON - - - - - - - - - - - - - - - - - # Miller Informational [Page 104] # RFC 7520 JOSE Cookbook May 2015 # The following is the assembled second recipient JSON: # { # "encrypted_key": "ExInT0io9BqBMYF6-maw5tZlgoZXThD1zWKsHixJuw # _elY4gSSId_w", # "header": { # "alg": "ECDH-ES+A256KW", # "kid": "peregrin.took@tuckborough.example", # "epk": { # "kty": "EC", # "crv": "P-384", # "x": "Uzdvk3pi5wKCRc1izp5_r0OjeqT-I68i8g2b8mva8diRhsE2xA # n2DtMRb25Ma2CX", # "y": "VDrRyFJh-Kwd1EjAgmj5Eo-CTHAZ53MC7PjjpLioy3ylEjI1pO # Mbw91fzZ84pbfm" # } # } # } # Figure 210: Recipient #2 JSON # 5.13.5. Encrypting the Key to the Third Recipient # The following is generated before encrypting the CEK for the third # recipient: # o Initialization Vector for key wrapping; this example uses the # Initialization Vector from Figure 211. # AvpeoPZ9Ncn9mkBn # Figure 211: Recipient #2 Initialization Vector for Key Wrapping, # base64url-encoded # Performing the "A256GCMKW" key encryption operation over the CEK # (Figure 202) with the following: # o AES symmetric key (Figure 138); and # o Initialization Vector (Figure 211) # produces the following: # o Encrypted Key from Figure 212. # o Authentication Tag from Figure 213. - - - # Miller Informational [Page 105] # RFC 7520 JOSE Cookbook May 2015 # a7CclAejo_7JSuPB8zeagxXRam8dwCfmkt9-WyTpS1E # Figure 212: Recipient #3 Encrypted Key, base64url-encoded # 59Nqh1LlYtVIhfD3pgRGvw # Figure 213: Recipient #3 Authentication Tag from Key Wrapping, # base64url-encoded # The following is generated after encrypting the CEK for the third # recipient: # o Recipient JWE Unprotected Header; this example uses the header # from Figure 214. # { # "alg": "A256GCMKW", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "tag": "59Nqh1LlYtVIhfD3pgRGvw", # "iv": "AvpeoPZ9Ncn9mkBn" # } # Figure 214: Recipient #3 JWE Per-Recipient Unprotected Header JSON # The following is the assembled third recipient JSON: # { # "encrypted_key": "a7CclAejo_7JSuPB8zeagxXRam8dwCfmkt9-WyTpS1 # E", # "header": { # "alg": "A256GCMKW", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "tag": "59Nqh1LlYtVIhfD3pgRGvw", # "iv": "AvpeoPZ9Ncn9mkBn" # } # } # Figure 215: Recipient #3 JSON # 5.13.6. Encrypting the Content # The following is generated before encrypting the content: # o JWE Protected Header; this example uses the header from # Figure 216, encoded to base64url [RFC4648] as Figure 217. - - - - # Miller Informational [Page 106] # RFC 7520 JOSE Cookbook May 2015 # { # "enc": "A128CBC-HS256" # } # Figure 216: JWE Protected Header JSON # eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0 # Figure 217: JWE Protected Header, base64url-encoded # Performing the content encryption operation over the Plaintext # (Figure 72) with the following: # o CEK (Figure 202), # o Initialization Vector (Figure 203), and # o JWE Protected Header (Figure 217) as the authenticated data # produces the following: # o Ciphertext from Figure 218. # o Authentication Tag from Figure 219. # ajm2Q-OpPXCr7-MHXicknb1lsxLdXxK_yLds0KuhJzfWK04SjdxQeSw2L9mu3a # _k1C55kCQ_3xlkcVKC5yr__Is48VOoK0k63_QRM9tBURMFqLByJ8vOYQX0oJW4 # VUHJLmGhF-tVQWB7Kz8mr8zeE7txF0MSaP6ga7-siYxStR7_G07Thd1jh-zGT0 # wxM5g-VRORtq0K6AXpLlwEqRp7pkt2zRM0ZAXqSpe1O6FJ7FHLDyEFnD-zDIZu # kLpCbzhzMDLLw2-8I14FQrgi-iEuzHgIJFIJn2wh9Tj0cg_kOZy9BqMRZbmYXM # Y9YQjorZ_P_JYG3ARAIF3OjDNqpdYe-K_5Q5crGJSDNyij_ygEiItR5jssQVH2 # ofDQdLChtazE # Figure 218: Ciphertext, base64url-encoded # BESYyFN7T09KY7i8zKs5_g # Figure 219: Authentication Tag, base64url-encoded - - - - - - - - - - - # Miller Informational [Page 107] # RFC 7520 JOSE Cookbook May 2015 # The following is generated after encrypting the Plaintext: # o JWE Shared Unprotected Header parameters; this example uses the # header from Figure 220. # { # "cty": "text/plain" # } # Figure 220: JWE Shared Unprotected Header JSON # 5.13.7. Output Results # The following compose the resulting JWE object: # o Recipient #1 JSON (Figure 206) # o Recipient #2 JSON (Figure 210) # o Recipient #3 JSON (Figure 215) # o Initialization Vector (Figure 203) # o Ciphertext (Figure 218) # o Authentication Tag (Figure 219) # The JWE Compact Serialization is not presented because it does not # support this use case; the flattened JWE JSON Serialization is not # presented because there is more than one recipient. - - - - - - - - - - - - - - - - - - - # Miller Informational [Page 108] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "dYOD28kab0Vvf4ODgxVAJXgHcSZICSOp8M51zj # wj4w6Y5G4XJQsNNIBiqyvUUAOcpL7S7-cFe7Pio7gV_Q06WmCSa- # vhW6me4bWrBf7cHwEQJdXihidAYWVajJIaKMXMvFRMV6iDlRr076 # DFthg2_AV0_tSiV6xSEIFqt1xnYPpmP91tc5WJDOGb-wqjw0-b-S # 1laS11QVbuP78dQ7Fa0zAVzzjHX-xvyM2wxj_otxr9clN1LnZMbe # YSrRicJK5xodvWgkpIdkMHo4LvdhRRvzoKzlic89jFWPlnBq_V4n # 5trGuExtp_-dbHcGlihqc_wGgho9fLMK8JOArYLcMDNQ", # "header": { # "alg": "RSA1_5", # "kid": "frodo.baggins@hobbiton.example" # } # }, # { # "encrypted_key": "ExInT0io9BqBMYF6-maw5tZlgoZXThD1zWKsHi # xJuw_elY4gSSId_w", # "header": { # "alg": "ECDH-ES+A256KW", # "kid": "peregrin.took@tuckborough.example", # "epk": { # "kty": "EC", # "crv": "P-384", # "x": "Uzdvk3pi5wKCRc1izp5_r0OjeqT-I68i8g2b8mva8diRhs # E2xAn2DtMRb25Ma2CX", # "y": "VDrRyFJh-Kwd1EjAgmj5Eo-CTHAZ53MC7PjjpLioy3ylEj # I1pOMbw91fzZ84pbfm" # } # } # }, # { # "encrypted_key": "a7CclAejo_7JSuPB8zeagxXRam8dwCfmkt9-Wy # TpS1E", # "header": { # "alg": "A256GCMKW", # "kid": "18ec08e1-bfa9-4d95-b205-2b4dd1d4321d", # "tag": "59Nqh1LlYtVIhfD3pgRGvw", # "iv": "AvpeoPZ9Ncn9mkBn" # } # } # ], # "unprotected": { # "cty": "text/plain" # }, # "protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", - # Miller Informational [Page 109] # RFC 7520 JOSE Cookbook May 2015 # "iv": "VgEIHY20EnzUtZFl2RpB1g", # "ciphertext": "ajm2Q-OpPXCr7-MHXicknb1lsxLdXxK_yLds0KuhJzfWK # 04SjdxQeSw2L9mu3a_k1C55kCQ_3xlkcVKC5yr__Is48VOoK0k63_QRM # 9tBURMFqLByJ8vOYQX0oJW4VUHJLmGhF-tVQWB7Kz8mr8zeE7txF0MSa # P6ga7-siYxStR7_G07Thd1jh-zGT0wxM5g-VRORtq0K6AXpLlwEqRp7p # kt2zRM0ZAXqSpe1O6FJ7FHLDyEFnD-zDIZukLpCbzhzMDLLw2-8I14FQ # rgi-iEuzHgIJFIJn2wh9Tj0cg_kOZy9BqMRZbmYXMY9YQjorZ_P_JYG3 # ARAIF3OjDNqpdYe-K_5Q5crGJSDNyij_ygEiItR5jssQVH2ofDQdLCht # azE", # "tag": "BESYyFN7T09KY7i8zKs5_g" # } # Figure 221: General JWE JSON Serialization # 6. Nesting Signatures and Encryption # This example illustrates nesting a JSON Web Signature (JWS) structure # within a JSON Web Encryption (JWE) structure. The signature uses the # "PS256" (RSASSA-PSS) algorithm; the encryption uses the "RSA-OAEP" # (RSAES-OAEP) key encryption algorithm and the "A128GCM" (AES-GCM) # content encryption algorithm. # Note that RSASSA-PSS uses random data to generate the signature, and # RSAES-OAEP uses random data to generate the ciphertext; it might not # be possible to exactly replicate the results in this section. # Note that whitespace is added for readability as described in # Section 1.1. # 6.1. Signing Input Factors # The following are supplied before beginning the signing operation: # o Payload content; this example uses the JSON Web Token [JWT] # content from Figure 222, encoded as base64url [RFC4648] to produce # Figure 223. # o RSA private key; this example uses the key from Figure 224. # o "alg" parameter of "PS256". # { # "iss": "hobbiton.example", # "exp": 1300819380, # "http://example.com/is_root": true # } # Figure 222: Payload Content, in JSON Format - # Miller Informational [Page 110] # RFC 7520 JOSE Cookbook May 2015 # eyJpc3MiOiJob2JiaXRvbi5leGFtcGxlIiwiZXhwIjoxMzAwODE5MzgwLCJodH # RwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZX0 # Figure 223: Payload Content, base64url-encoded # { # "kty": "RSA", # "kid": "hobbiton.example", # "use": "sig", # "n": "kNrPIBDXMU6fcyv5i-QHQAQ-K8gsC3HJb7FYhYaw8hXbNJa-t8q0lD # KwLZgQXYV-ffWxXJv5GGrlZE4GU52lfMEegTDzYTrRQ3tepgKFjMGg6I # y6fkl1ZNsx2gEonsnlShfzA9GJwRTmtKPbk1s-hwx1IU5AT-AIelNqBg # cF2vE5W25_SGGBoaROVdUYxqETDggM1z5cKV4ZjDZ8-lh4oVB07bkac6 # LQdHpJUUySH_Er20DXx30Kyi97PciXKTS-QKXnmm8ivyRCmux22ZoPUi # nd2BKC5OiG4MwALhaL2Z2k8CsRdfy-7dg7z41Rp6D0ZeEvtaUp4bX4aK # raL4rTfw", # "e": "AQAB", # "d": "ZLe_TIxpE9-W_n2VBa-HWvuYPtjvxwVXClJFOpJsdea8g9RMx34qEO # EtnoYc2un3CZ3LtJi-mju5RAT8YSc76YJds3ZVw0UiO8mMBeG6-iOnvg # obobNx7K57-xjTJZU72EjOr9kB7z6ZKwDDq7HFyCDhUEcYcHFVc7iL_6 # TibVhAhOFONWlqlJgEgwVYd0rybNGKifdnpEbwyHoMwY6HM1qvnEFgP7 # iZ0YzHUT535x6jj4VKcdA7ZduFkhUauysySEW7mxZM6fj1vdjJIy9LD1 # fIz30Xv4ckoqhKF5GONU6tNmMmNgAD6gIViyEle1PrIxl1tBhCI14bRW # -zrpHgAQ", # "p": "yKWYoNIAqwMRQlgIBOdT1NIcbDNUUs2Rh-pBaxD_mIkweMt4Mg-0-B # 2iSYvMrs8horhonV7vxCQagcBAATGW-hAafUehWjxWSH-3KccRM8toL4 # e0q7M-idRDOBXSoe7Z2-CV2x_ZCY3RP8qp642R13WgXqGDIM4MbUkZSj # cY9-c", # "q": "uND4o15V30KDzf8vFJw589p1vlQVQ3NEilrinRUPHkkxaAzDzccGgr # WMWpGxGFFnNL3w5CqPLeU76-5IVYQq0HwYVl0hVXQHr7sgaGu-483Ad3 # ENcL23FrOnF45m7_2ooAstJDe49MeLTTQKrSIBl_SKvqpYvfSPTczPcZ # kh9Kk", # "dp": "jmTnEoq2qqa8ouaymjhJSCnsveUXnMQC2gAneQJRQkFqQu-zV2PKP # KNbPvKVyiF5b2-L3tM3OW2d2iNDyRUWXlT7V5l0KwPTABSTOnTqAmYCh # Gi8kXXdlhcrtSvXldBakC6saxwI_TzGGY2MVXzc2ZnCvCXHV4qjSxOrf # P3pHFU", # "dq": "R9FUvU88OVzEkTkXl3-5-WusE4DjHmndeZIlu3rifBdfLpq_P-iWP # BbGaq9wzQ1c-J7SzCdJqkEJDv5yd2C7rnZ6kpzwBh_nmL8zscAk1qsun # nt9CJGAYz7-sGWy1JGShFazfP52ThB4rlCJ0YuEaQMrIzpY77_oLAhpm # DA0hLk", # "qi": "S8tC7ZknW6hPITkjcwttQOPLVmRfwirRlFAViuDb8NW9CrV_7F2Oq # UZCqmzHTYAumwGFHI1WVRep7anleWaJjxC_1b3fq_al4qH3Pe-EKiHg6 # IMazuRtZLUROcThrExDbF5dYbsciDnfRUWLErZ4N1Be0bnxYuPqxwKd9 # QZwMo0" # } # Figure 224: RSA 2048-Bit Private Key, in JWK Format - - # Miller Informational [Page 111] # RFC 7520 JOSE Cookbook May 2015 # 6.2. Signing Operation # The following is generated to complete the signing operation: # o JWS Protected Header; this example uses the header from # Figure 225, encoded using base64url [RFC4648] to produce # Figure 226. # { # "alg": "PS256", # "typ": "JWT" # } # Figure 225: JWS Protected Header JSON # eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9 # Figure 226: JWS Protected Header, base64url-encoded # Performing the signature operation over the combined JWS Protected # Header (Figure 226) and payload content (Figure 222) produces the # following signature: # dPpMqwRZxFYi1UfcDAaf8M99o7kwUWtiXZ-ByvVuJih4MhJ_aZqciprz0OWaIA # kIvn1qskChirjKvY9ESZNUCP4JjvfyPS-nqjJxYoA5ztWOyFk2cZNIPXjcJXSQ # wXPO9tEe-v4VSqgD0aKHqPxYog4N6Cz1lKph1U1sYDSI67_bLL7elg_vkjfMp5 # _W5l5LuUYGMeh6hxQIaIUXf9EwV2JmvTMuZ-vBOWy0Sniy1EFo72CRTvmtrIf5 # AROo5MNliY3KtUxeP-SOmD-LEYwW9SlkohYzMVAZDDOrVbv7KVRHpeYNaK75KE # QqdCEEkS_rskZS-Qtt_nlegTWh1mEYaA # Figure 227: JWS Signature, base64url-encoded # 6.3. Signing Output # The following compose the resulting JWS object: # o JWS Protected Header (Figure 226) # o Payload content (Figure 223) # o Signature (Figure 227) - - - - - - - - # Miller Informational [Page 112] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWS object using the JWS Compact Serialization (which # is the plaintext input to the following encryption operation): # eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9 # . # eyJpc3MiOiJob2JiaXRvbi5leGFtcGxlIiwiZXhwIjoxMzAwODE5MzgwLCJodH # RwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZX0 # . # dPpMqwRZxFYi1UfcDAaf8M99o7kwUWtiXZ-ByvVuJih4MhJ_aZqciprz0OWaIA # kIvn1qskChirjKvY9ESZNUCP4JjvfyPS-nqjJxYoA5ztWOyFk2cZNIPXjcJXSQ # wXPO9tEe-v4VSqgD0aKHqPxYog4N6Cz1lKph1U1sYDSI67_bLL7elg_vkjfMp5 # _W5l5LuUYGMeh6hxQIaIUXf9EwV2JmvTMuZ-vBOWy0Sniy1EFo72CRTvmtrIf5 # AROo5MNliY3KtUxeP-SOmD-LEYwW9SlkohYzMVAZDDOrVbv7KVRHpeYNaK75KE # QqdCEEkS_rskZS-Qtt_nlegTWh1mEYaA # Figure 228: JWS Compact Serialization # 6.4. Encryption Input Factors # The following are supplied before beginning the encryption process: # o Plaintext content; this example uses the content from Figure 228. # o RSA public key; this example uses the key from Figure 84. # o "alg" parameter of "RSA-OAEP". # o "enc" parameter of "A128GCM". # 6.5. Encryption Generated Factors # The following are generated before encrypting: # o AES symmetric key as the Content Encryption Key (CEK); this # example uses the key from Figure 229. # o Initialization Vector; this example uses the Initialization Vector # from Figure 230. # 0RHSNYwN-6-2QBGsYTZLSQ # Figure 229: Content Encryption Key, base64url-encoded # GbX1i9kXz0sxXPmA # Figure 230: Initialization Vector, base64url-encoded - - - # Miller Informational [Page 113] # RFC 7520 JOSE Cookbook May 2015 # 6.6. Encrypting the Key # Performing the key encryption operation over the CEK (Figure 229) # with the RSA key (Figure 84) produces the following Encrypted Key: # a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4 # E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4g # zMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21 # O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5I # R7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDU # F_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-I # apSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZ # a38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAa # mBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblp # ymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_x # pkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO # 0 # Figure 231: Encrypted Key, base64url-encoded # 6.7. Encrypting the Content # The following is generated before encrypting the Plaintext: # o JWE Protected Header; this example uses the header from # Figure 232, encoded using base64url [RFC4648] to produce # Figure 233. # { # "alg": "RSA-OAEP", # "cty": "JWT", # "enc": "A128GCM" # } # Figure 232: JWE Protected Header JSON # eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ # Figure 233: JWE Protected Header, base64url-encoded - - - - - - - - - - # Miller Informational [Page 114] # RFC 7520 JOSE Cookbook May 2015 # Performing the content encryption operation over the Plaintext # (Figure 228) with the following: # o CEK (Figure 229); # o Initialization Vector (Figure 230); and # o JWE Protected Header (Figure 233) as authenticated data # produces the following: # o Ciphertext from Figure 234. # o Authentication Tag from Figure 235. # SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2Q # kxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLX # qZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZ # TNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9C # hMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEa # ulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2 # zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUr # lx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLf # hn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc # 9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkB # siXMCjy1Noue7MD-ShDp5dmM # Figure 234: Ciphertext, base64url-encoded # KnIKEhN8U-3C9s4gtSpjSw # Figure 235: Authentication Tag, base64url-encoded # 6.8. Encryption Output # The following compose the resulting JWE object: # o JWE Protected Header (Figure 233) # o Encrypted Key (Figure 231) # o Initialization Vector (Figure 230) # o Ciphertext (Figure 234) # o Authentication Tag (Figure 235) - - - # Miller Informational [Page 115] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the JWE Compact Serialization: # eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYyI6IkExMjhHQ00ifQ # . # a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurCyrBg3I7YhCRYjphDOOS4 # E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4g # zMedpiJHQVeyI-zkZV7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21 # O0ul4YxSHV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5I # R7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDU # F_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-I # apSjVFnMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZ # a38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAa # mBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblp # ymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDhi1smxS_X_x # pkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_GnVrNwlK7Lgxw6FSQvDO # 0 # . # GbX1i9kXz0sxXPmA # . # SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBNgCe2OFMf66cSJ8k2Q # kxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAvVLsAXB0_UTCBGyBg3C2bWLX # qZlfJAAoJRUPRk-BimYZY81zVBuIhc7HsQePCpu33SzMsFHjn4lP_idrJz_glZ # TNgKDt8zdnUPauKTKDNOH1DD4fuzvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9C # hMPqW1QNhzuX_Zul3bvrJwr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEa # ulV18l4Fg9tLejdkAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2 # zI3Q_1sYjKUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUr # lx4gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gYjLf # hn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5XmnwZMyNc # 9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMxtlMizR88NKU1WkB # siXMCjy1Noue7MD-ShDp5dmM # . # KnIKEhN8U-3C9s4gtSpjSw # Figure 236: JWE Compact Serialization - - - - - - - - - - - - - - - # Miller Informational [Page 116] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the general JWE JSON Serialization: # { # "recipients": [ # { # "encrypted_key": "a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVh # jurCyrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVO # GrG-bi13mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV # 7A9matpgevAJWrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxS # HV-ByK1kyeetRp_fuYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e # 5IR7nany-25_UmC2urosNkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5 # o6yV64x6yzDUF_5JCIdl-Qv6H5dMVIY7q1eKpXcV1lWO_2FefEBq # XxXvIjLeZivjNkzogCq3-IapSjVFnMjBxjpYLT8muaawo1yy1XXM # uinIpNcOY3n4KKrXLrCcteX85m4IIHMZa38s1Hpr56fPPseMA-Jl # tmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3kJusAamBKOYwfk7J # hLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15qJIEXNJtqnblp # ymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TXuPC8yDDh # i1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX2Fo_ # GnVrNwlK7Lgxw6FSQvDO0" # } # ], # "protected": "eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYy # I6IkExMjhHQ00ifQ", # "iv": "GbX1i9kXz0sxXPmA", # "ciphertext": "SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBN # gCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAv # VLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7Hs # QePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fu # zvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJ # wr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejd # kAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYj # KUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4 # gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gY # jLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5 # XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMx # tlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM", # "tag": "KnIKEhN8U-3C9s4gtSpjSw" # } # Figure 237: General JWE JSON Serialization - - - - - - - - - # Miller Informational [Page 117] # RFC 7520 JOSE Cookbook May 2015 # The resulting JWE object using the flattened JWE JSON Serialization: # { # "encrypted_key": "a0JHRoITfpX4qRewImjlStn8m3CPxBV1ueYlVhjurC # yrBg3I7YhCRYjphDOOS4E7rXbr2Fn6NyQq-A-gqT0FXqNjVOGrG-bi13 # mwy7RoYhjTkBEC6P7sMYMXXx4gzMedpiJHQVeyI-zkZV7A9matpgevAJ # WrXzOUysYGTtwoSN6gtUVtlLaivjvb21O0ul4YxSHV-ByK1kyeetRp_f # uYJxHoKLQL9P424sKx2WGYb4zsBIPF4ssl_e5IR7nany-25_UmC2uros # NkoFz9cQ82MypZP8gqbQJyPN-Fpp4Z-5o6yV64x6yzDUF_5JCIdl-Qv6 # H5dMVIY7q1eKpXcV1lWO_2FefEBqXxXvIjLeZivjNkzogCq3-IapSjVF # nMjBxjpYLT8muaawo1yy1XXMuinIpNcOY3n4KKrXLrCcteX85m4IIHMZ # a38s1Hpr56fPPseMA-Jltmt-a9iEDtOzhtxz8AXy9tsCAZV2XBWNG8c3 # kJusAamBKOYwfk7JhLRDgOnJjlJLhn7TI4UxDp9dCmUXEN6z0v23W15q # JIEXNJtqnblpymooeWAHCT4e_Owbim1g0AEpTHUdA2iiLNs9WTX_H_TX # uPC8yDDhi1smxS_X_xpkIHkiIHWDOLx03BpqDTivpKkBYwqP2UZkcxqX # 2Fo_GnVrNwlK7Lgxw6FSQvDO0", # "protected": "eyJhbGciOiJSU0EtT0FFUCIsImN0eSI6IkpXVCIsImVuYy # I6IkExMjhHQ00ifQ", # "iv": "GbX1i9kXz0sxXPmA", # "ciphertext": "SZI4IvKHmwpazl_pJQXX3mHv1ANnOU4Wf9-utWYUcKrBN # gCe2OFMf66cSJ8k2QkxaQD3_R60MGE9ofomwtky3GFxMeGRjtpMt9OAv # VLsAXB0_UTCBGyBg3C2bWLXqZlfJAAoJRUPRk-BimYZY81zVBuIhc7Hs # QePCpu33SzMsFHjn4lP_idrJz_glZTNgKDt8zdnUPauKTKDNOH1DD4fu # zvDYfDIAfqGPyL5sVRwbiXpXdGokEszM-9ChMPqW1QNhzuX_Zul3bvrJ # wr7nuGZs4cUScY3n8yE3AHCLurgls-A9mz1X38xEaulV18l4Fg9tLejd # kAuQZjPbqeHQBJe4IwGD5Ee0dQ-Mtz4NnhkIWx-YKBb_Xo2zI3Q_1sYj # KUuis7yWW-HTr_vqvFt0bj7WJf2vzB0TZ3dvsoGaTvPH2dyWwumUrlx4 # gmPUzBdwTO6ubfYSDUEEz5py0d_OtWeUSYcCYBKD-aM7tXg26qJo21gY # jLfhn9zy-W19sOCZGuzgFjPhawXHpvnj_t-0_ES96kogjJLxS1IMU9Y5 # XmnwZMyNc9EIwnogsCg-hVuvzyP0sIruktmI94_SL1xgMl7o03phcTMx # tlMizR88NKU1WkBsiXMCjy1Noue7MD-ShDp5dmM", # "tag": "KnIKEhN8U-3C9s4gtSpjSw" # } # Figure 238: Flattened JWE JSON Serialization - - - - - - - - - - - - - - # Miller Informational [Page 118] # RFC 7520 JOSE Cookbook May 2015 # 7. Security Considerations # This document is designed to provide examples for developers to use # in checking their implementations. As such, it does not follow some # of the security considerations and recommendations in the core # documents (i.e., [JWS], [JWE], [JWK], and [JWA]). For instance: # o it does not always generate a new CEK value for every encrypted # example; # o it does not always generate a new Initialization Vector (IV) value # for every encrypted example; and # o it does not always generate a new ephemeral key for every # ephemeral key example. # For each example, data that is expected to be generated for each # signing or encryption operation is isolated to sections titled # "Generated Factors". # 8. References # 8.1. Normative References # [JWA] Jones, M., "JSON Web Algorithms (JWA)", RFC 7518, # DOI 10.17487/RFC7518, May 2015, # . # [JWE] Jones, M. and J. Hildebrand, "JSON Web Encryption (JWE)", # RFC 7516, DOI 10.17487/RFC7516, May 2015, # . # [JWK] Jones, M., "JSON Web Key (JWK)", RFC 7517, # DOI 10.17487/RFC7517, May 2015, # . # [JWS] Jones, M., Bradley, J., and N. Sakimura, "JSON Web # Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May # 2015, . # [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data # Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, # . - - - - - - # Miller Informational [Page 119] # RFC 7520 JOSE Cookbook May 2015 # 8.2. Informative References # [JWT] Jones, M., Bradley, J., and N. Sakimura, "JSON Web Token # (JWT)", RFC 7519, DOI 10.17487/RFC7519, May 2015, # . # [LOTR-FELLOWSHIP] # Tolkien, J., "The Fellowship of the Ring", HarperCollins # Publishers, ePub Edition, ISBN 9780061952838, March 2009. # [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format Specification # version 1.3", RFC 1951, DOI 10.17487/RFC1951, May 1996, # . # [RFC7095] Kewisch, P., "jCard: The JSON Format for vCard", RFC 7095, # DOI 10.17487/RFC7095, January 2014, # . # Acknowledgements # Most of the examples herein use quotes and character names found in # the novel "The Fellowship of the Ring" [LOTR-FELLOWSHIP], written by # J. R. R. Tolkien. # Thanks to Richard Barnes, Brian Campbell, Mike Jones, and Jim Schaad # for their input and review of the text. Thanks to Brian Campbell for # verifying the Compact Serialization examples. # Author's Address # Matthew Miller # Cisco Systems, Inc. # EMail: mamille2@cisco.com - - - - - - - - - - - - - - - # Miller Informational [Page 120] # Html markup produced by rfcmarkup 1.116, available from https://tools.ietf.org/tools/rfcmarkup/ diff --git a/tests/rfc/test_rfc8037.py b/tests/rfc/test_rfc8037.py deleted file mode 100644 index f534640..0000000 --- a/tests/rfc/test_rfc8037.py +++ /dev/null @@ -1,793 +0,0 @@ - -# Disable flake8 reporting -# flake8: noqa - -from jose.jws import verify - -expected_payload = b"It\xe2\x80\x99s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there\xe2\x80\x99s no knowing where you might be swept off to." - - -# [Docs] [txt|pdf] [draft-ietf-jose-c...] - - - -# Internet Engineering Task Force (IETF) I. Liusvaara -# Request for Comments: 8037 Independent -# Category: Standards Track January 2017 -# ISSN: 2070-1721 - - -# CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures -# in JSON Object Signing and Encryption (JOSE) - -# Abstract - -# This document defines how to use the Diffie-Hellman algorithms -# "X25519" and "X448" as well as the signature algorithms "Ed25519" and -# "Ed448" from the IRTF CFRG elliptic curves work in JSON Object -# Signing and Encryption (JOSE). - -# Status of This Memo - -# This is an Internet Standards Track document. - -# This document is a product of the Internet Engineering Task Force -# (IETF). It represents the consensus of the IETF community. It has -# received public review and has been approved for publication by the -# Internet Engineering Steering Group (IESG). Further information on -# Internet Standards is available in Section 2 of RFC 7841. - -# Information about the current status of this document, any errata, -# and how to provide feedback on it may be obtained at -# http://www.rfc-editor.org/info/rfc8037. - -# Copyright Notice - -# Copyright (c) 2017 IETF Trust and the persons identified as the -# document authors. All rights reserved. - -# This document is subject to BCP 78 and the IETF Trust's Legal -# Provisions Relating to IETF Documents -# (http://trustee.ietf.org/license-info) in effect on the date of -# publication of this document. Please review these documents -# carefully, as they describe your rights and restrictions with respect -# to this document. Code Components extracted from this document must -# include Simplified BSD License text as described in Section 4.e of -# the Trust Legal Provisions and are provided without warranty as -# described in the Simplified BSD License. - - - - - - - -# Liusvaara Standards Track [Page 1] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# Table of Contents - -# 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 -# 1.1. Terminology . . . . . . . . . . . . . . . . . . . . . . . 3 -# 2. Key Type "OKP" . . . . . . . . . . . . . . . . . . . . . . . 3 -# 3. Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . 4 -# 3.1. Signatures . . . . . . . . . . . . . . . . . . . . . . . 4 -# 3.1.1. Signing . . . . . . . . . . . . . . . . . . . . . . . 4 -# 3.1.2. Verification . . . . . . . . . . . . . . . . . . . . 4 -# 3.2. ECDH-ES . . . . . . . . . . . . . . . . . . . . . . . . . 4 -# 3.2.1. Performing the ECDH Operation . . . . . . . . . . . . 5 -# 4. Security Considerations . . . . . . . . . . . . . . . . . . . 5 -# 5. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 6 -# 6. References . . . . . . . . . . . . . . . . . . . . . . . . . 8 -# 6.1. Normative References . . . . . . . . . . . . . . . . . . 8 -# 6.2. Informative References . . . . . . . . . . . . . . . . . 8 -# Appendix A. Examples . . . . . . . . . . . . . . . . . . . . . . 9 -# A.1. Ed25519 Private Key . . . . . . . . . . . . . . . . . . . 9 -# A.2. Ed25519 Public Key . . . . . . . . . . . . . . . . . . . 9 -# A.3. JWK Thumbprint Canonicalization . . . . . . . . . . . . . 9 -# A.4. Ed25519 Signing . . . . . . . . . . . . . . . . . . . . . 10 -# A.5. Ed25519 Validation . . . . . . . . . . . . . . . . . . . 11 -# A.6. ECDH-ES with X25519 . . . . . . . . . . . . . . . . . . . 11 -# A.7. ECDH-ES with X448 . . . . . . . . . . . . . . . . . . . . 12 -# Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . 14 -# Author's Address . . . . . . . . . . . . . . . . . . . . . . . . 14 - -# 1. Introduction - -# The Internet Research Task Force (IRTF) Crypto Forum Research Group -# (CFRG) selected new Diffie-Hellman algorithms ("X25519" and "X448"; -# [RFC7748]) and signature algorithms ("Ed25519" and "Ed448"; -# [RFC8032]) for asymmetric key cryptography. This document defines -# how to use those algorithms in JOSE in an interoperable manner. - -# This document defines the conventions to use in the context of -# [RFC7515], [RFC7516], and [RFC7517]. - -# While the CFRG also defined two pairs of isogenous elliptic curves -# that underlie these algorithms, these curves are not directly -# exposed, as the algorithms laid on top are sufficient for the -# purposes of JOSE and are much easier to use. - -# All inputs to and outputs from the Elliptic Curve Diffie-Hellman -# (ECDH) and signature functions are defined to be octet strings, with -# the exception of outputs of verification functions, which are -# booleans. - - - - -# Liusvaara Standards Track [Page 2] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# 1.1. Terminology - -# The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -# "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this -# document are to be interpreted as described in [RFC2119]. - -# "JWS Signing Input" and "JWS Signature" are defined by [RFC7515]. - -# "Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static" -# is defined by Section 4.6 of [RFC7518]. - -# The JOSE key format ("JSON Web Key (JWK)") is defined by [RFC7517] -# and thumbprints for it ("JSON Web Key (JWK) Thumbprint") in -# [RFC7638]. - -# 2. Key Type "OKP" - -# A new key type (kty) value "OKP" (Octet Key Pair) is defined for -# public key algorithms that use octet strings as private and public -# keys. It has the following parameters: - -# o The parameter "kty" MUST be "OKP". - -# o The parameter "crv" MUST be present and contain the subtype of the -# key (from the "JSON Web Elliptic Curve" registry). - -# o The parameter "x" MUST be present and contain the public key -# encoded using the base64url [RFC4648] encoding. - -# o The parameter "d" MUST be present for private keys and contain the -# private key encoded using the base64url encoding. This parameter -# MUST NOT be present for public keys. - -# Note: Do not assume that there is an underlying elliptic curve, -# despite the existence of the "crv" and "x" parameters. (For -# instance, this key type could be extended to represent Diffie-Hellman -# (DH) algorithms based on hyperelliptic surfaces.) - -# When calculating JWK Thumbprints [RFC7638], the three public key -# fields are included in the hash input in lexicographic order: "crv", -# "kty", and "x". - - - - - - - - - - -# Liusvaara Standards Track [Page 3] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# 3. Algorithms - -# 3.1. Signatures - -# For the purpose of using the Edwards-curve Digital Signature -# Algorithm (EdDSA) for signing data using "JSON Web Signature (JWS)" -# [RFC7515], algorithm "EdDSA" is defined here, to be applied as the -# value of the "alg" parameter. - -# The following key subtypes are defined here for use with EdDSA: - -# "crv" EdDSA Variant -# Ed25519 Ed25519 -# Ed448 Ed448 - -# The key type used with these keys is "OKP" and the algorithm used for -# signing is "EdDSA". These subtypes MUST NOT be used for Elliptic -# Curve Diffie-Hellman Ephemeral Static (ECDH-ES). - -# The EdDSA variant used is determined by the subtype of the key -# (Ed25519 for "Ed25519" and Ed448 for "Ed448"). - -# 3.1.1. Signing - -# Signing for these is performed by applying the signing algorithm -# defined in [RFC8032] to the private key (as private key), public key -# (as public key), and the JWS Signing Input (as message). The -# resulting signature is the JWS Signature. All inputs and outputs are -# octet strings. - -# 3.1.2. Verification - -# Verification is performed by applying the verification algorithm -# defined in [RFC8032] to the public key (as public key), the JWS -# Signing Input (as message), and the JWS Signature (as signature). -# All inputs are octet strings. If the algorithm accepts, the -# signature is valid; otherwise, the signature is invalid. - -# 3.2. ECDH-ES - -# The following key subtypes are defined here for purpose of "Key -# Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static" -# (ECDH-ES): - -# "crv" ECDH Function Applied -# X25519 X25519 -# X448 X448 - - - - -# Liusvaara Standards Track [Page 4] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# The key type used with these keys is "OKP". These subtypes MUST NOT -# be used for signing. - -# Section 4.6 of [RFC7518] defines the ECDH-ES algorithms -# "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW", and "ECDH-ES". - -# 3.2.1. Performing the ECDH Operation - -# The "x" parameter of the "epk" field is set as follows: - -# Apply the appropriate ECDH function to the ephemeral private key (as -# scalar input) and the standard base point (as u-coordinate input). -# The base64url encoding of the output is the value for the "x" -# parameter of the "epk" field. All inputs and outputs are octet -# strings. - -# The Z value (raw key agreement output) for key agreement (to be used -# in subsequent Key Derivation Function (KDF) as per Section 4.6.2 of -# [RFC7518]) is determined as follows: - -# Apply the appropriate ECDH function to the ephemeral private key (as -# scalar input) and receiver public key (as u-coordinate input). The -# output is the Z value. All inputs and outputs are octet strings. - -# 4. Security Considerations - -# Security considerations from [RFC7748] and [RFC8032] apply here. - -# Do not separate key material from information about what key subtype -# it is for. When using keys, check that the algorithm is compatible -# with the key subtype for the key. To do otherwise opens the system -# up to attacks via mixing up algorithms. It is particularly dangerous -# to mix up signature and Message Authentication Code (MAC) algorithms. - -# Although for Ed25519 and Ed448, the signature binds the key used for -# signing, do not assume this, as there are many signature algorithms -# that fail to make such a binding. If key-binding is desired, include -# the key used for signing either inside the JWS protected header or -# the data to sign. - -# If key generation or batch signature verification is performed, a -# well-seeded cryptographic random number generator is REQUIRED. -# Signing and non-batch signature verification are deterministic -# operations and do not need random numbers of any kind. - - - - - - - -# Liusvaara Standards Track [Page 5] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# The JSON Web Algorithm (JWA) ECDH-ES KDF construction does not mix -# keys into the final shared secret. In key exchange, such mixing -# could be a bad mistake; whereas here either the receiver public key -# has to be chosen maliciously or the sender has to be malicious in -# order to cause problems. In either case, all security evaporates. - -# The nominal security strengths of X25519 and X448 are ~126 and ~223 -# bits. Therefore, using 256-bit symmetric encryption (especially key -# wrapping and encryption) with X448 is RECOMMENDED. - -# 5. IANA Considerations - -# The following has been added to the "JSON Web Key Types" registry: - -# o "kty" Parameter Value: "OKP" -# o Key Type Description: Octet string key pairs -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG -# o Specification Document(s): Section 2 of RFC 8037 - -# The following has been added to the "JSON Web Key Parameters" -# registry: - -# o Parameter Name: "crv" -# o Parameter Description: The subtype of key pair -# o Parameter Information Class: Public -# o Used with "kty" Value(s): "OKP" -# o Change Controller: IESG -# o Specification Document(s): Section 2 of RFC 8037 - -# o Parameter Name: "d" -# o Parameter Description: The private key -# o Parameter Information Class: Private -# o Used with "kty" Value(s): "OKP" -# o Change Controller: IESG -# o Specification Document(s): Section 2 of RFC 8037 - -# o Parameter Name: "x" -# o Parameter Description: The public key -# o Parameter Information Class: Public -# o Used with "kty" Value(s): "OKP" -# o Change Controller: IESG -# o Specification Document(s): Section 2 of RFC 8037 - - - - - - - - -# Liusvaara Standards Track [Page 6] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# The following has been added to the "JSON Web Signature and -# Encryption Algorithms" registry: - -# o Algorithm Name: "EdDSA" -# o Algorithm Description: EdDSA signature algorithms -# o Algorithm Usage Location(s): "alg" -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG - -# o Specification Document(s): Section 3.1 of RFC 8037 -# o Algorithm Analysis Documents(s): [RFC8032] - -# The following has been added to the "JSON Web Key Elliptic Curve" -# registry: - -# o Curve Name: "Ed25519" -# o Curve Description: Ed25519 signature algorithm key pairs -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG -# o Specification Document(s): Section 3.1 of RFC 8037 - -# o Curve Name: "Ed448" -# o Curve Description: Ed448 signature algorithm key pairs -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG -# o Specification Document(s): Section 3.1 of RFC 8037 - -# o Curve name: "X25519" -# o Curve Description: X25519 function key pairs -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG -# o Specification Document(s): Section 3.2 of RFC 8037 -# o Analysis Documents(s): [RFC7748] - -# o Curve Name: "X448" -# o Curve Description: X448 function key pairs -# o JOSE Implementation Requirements: Optional -# o Change Controller: IESG -# o Specification Document(s): Section 3.2 of RFC 8037 -# o Analysis Documents(s): [RFC7748] - - - - - - - - - - - -# Liusvaara Standards Track [Page 7] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# 6. References - -# 6.1. Normative References - -# [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate -# Requirement Levels", BCP 14, RFC 2119, -# DOI 10.17487/RFC2119, March 1997, -# . - -# [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data -# Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, -# . - -# [RFC7515] Jones, M., Bradley, J., and N. Sakimura, "JSON Web -# Signature (JWS)", RFC 7515, DOI 10.17487/RFC7515, May -# 2015, . - -# [RFC7517] Jones, M., "JSON Web Key (JWK)", RFC 7517, -# DOI 10.17487/RFC7517, May 2015, -# . - -# [RFC7518] Jones, M., "JSON Web Algorithms (JWA)", RFC 7518, -# DOI 10.17487/RFC7518, May 2015, -# . - -# [RFC7638] Jones, M. and N. Sakimura, "JSON Web Key (JWK) -# Thumbprint", RFC 7638, DOI 10.17487/RFC7638, September -# 2015, . - -# [RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves -# for Security", RFC 7748, DOI 10.17487/RFC7748, January -# 2016, . - -# [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital -# Signature Algorithm (EdDSA)", RFC 8032, -# DOI 10.17487/RFC8032, January 2017, -# . - -# 6.2. Informative References - -# [RFC7516] Jones, M. and J. Hildebrand, "JSON Web Encryption (JWE)", -# RFC 7516, DOI 10.17487/RFC7516, May 2015, -# . - - - - - - - - -# Liusvaara Standards Track [Page 8] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# Appendix A. Examples - -# To the extent possible, these examples use material taken from test -# vectors of [RFC7748] and [RFC8032]. - -# A.1. Ed25519 Private Key - -# {"kty":"OKP","crv":"Ed25519", -# "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", -# "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"} - -# The hexadecimal dump of private key is: - -# 9d 61 b1 9d ef fd 5a 60 ba 84 4a f4 92 ec 2c c4 -# 44 49 c5 69 7b 32 69 19 70 3b ac 03 1c ae 7f 60 - -# And of the public key is: - -# d7 5a 98 01 82 b1 0a b7 d5 4b fe d3 c9 64 07 3a -# 0e e1 72 f3 da a6 23 25 af 02 1a 68 f7 07 51 1a - -# A.2. Ed25519 Public Key - -# This is the public part of the previous private key (which just omits -# "d"): - -# {"kty":"OKP","crv":"Ed25519", -# "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"} - -# A.3. JWK Thumbprint Canonicalization - -# The JWK Thumbprint canonicalization of the two examples above (with a -# linebreak inserted for formatting reasons) is: - -# {"crv":"Ed25519","kty":"OKP","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwI -# aaPcHURo"} - -# Which has the SHA-256 hash (in hexadecimal) of -# 90facafea9b1556698540f70c0117a22ea37bd5cf3ed3c47093c1707282b4b89, -# which results in the base64url encoded JWK Thumbprint representation -# of "kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k". - - - - - - - - - - -# Liusvaara Standards Track [Page 9] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# A.4. Ed25519 Signing - -# The JWS protected header is: - -# {"alg":"EdDSA"} - -# This has the base64url encoding of: - -# eyJhbGciOiJFZERTQSJ9 - -# The payload is (text): - -# Example of Ed25519 signing - -# This has the base64url encoding of: - -# RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc - -# The JWS signing input is (a concatenation of base64url encoding of -# the (protected) header, a dot, and base64url encoding of the payload) -# is: - -# eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc - -# Applying the Ed25519 signing algorithm using the private key, public -# key, and the JWS signing input yields the signature (hex): - -# 86 0c 98 d2 29 7f 30 60 a3 3f 42 73 96 72 d6 1b -# 53 cf 3a de fe d3 d3 c6 72 f3 20 dc 02 1b 41 1e -# 9d 59 b8 62 8d c3 51 e2 48 b8 8b 29 46 8e 0e 41 -# 85 5b 0f b7 d8 3b b1 5b e9 02 bf cc b8 cd 0a 02 - -# Converting this to base64url yields: - -# hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt -# 9g7sVvpAr_MuM0KAg - -# So the compact serialization of the JWS is (a concatenation of -# signing input, a dot, and base64url encoding of the signature): - -# eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCj -# P0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_Mu -# M0KAg - - - - - - - - -# Liusvaara Standards Track [Page 10] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# A.5. Ed25519 Validation - -# The JWS from the example above is: - -# eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCj -# P0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_Mu -# M0KAg - -# This has 2 dots in it, so it might be valid a JWS. Base64url -# decoding the protected header yields: - -# {"alg":"EdDSA"} - -# So this is an EdDSA signature. Now the key has: "kty":"OKP" and -# "crv":"Ed25519", so the signature is Ed25519 signature. - -# The signing input is the part before the second dot: - -# eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc - -# Applying the Ed25519 verification algorithm to the public key, JWS -# signing input, and the signature yields true. So the signature is -# valid. The message is the base64url decoding of the part between the -# dots: - -# Example of Ed25519 Signing - -# A.6. ECDH-ES with X25519 - -# The public key to encrypt to is: - -# {"kty":"OKP","crv":"X25519","kid":"Bob", -# "x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"} - -# The public key from the target key is (hex): - -# de 9e db 7d 7b 7d c1 b4 d3 5b 61 c2 ec e4 35 37 -# 3f 83 43 c8 5b 78 67 4d ad fc 7e 14 6f 88 2b 4f - -# The ephemeral secret happens to be (hex): - -# 77 07 6d 0a 73 18 a5 7d 3c 16 c1 72 51 b2 66 45 -# df 4c 2f 87 eb c0 99 2a b1 77 fb a5 1d b9 2c 2a - -# So the ephemeral public key is X25519(ephkey, G) (hex): - -# 85 20 f0 09 89 30 a7 54 74 8b 7d dc b4 3e f7 5a -# 0d bf 3a 0d 26 38 1a f4 eb a4 a9 8e aa 9b 4e 6a - - - -# Liusvaara Standards Track [Page 11] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# This is represented as the ephemeral public key value: - -# {"kty":"OKP","crv":"X25519", -# "x":"hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo"} - -# So the protected header could be, for example: - -# {"alg":"ECDH-ES+A128KW","epk":{"kty":"OKP","crv":"X25519", -# "x":"hSDwCYkwp1R0i33ctD73Wg2_Og0mOBr066SpjqqbTmo"}, -# "enc":"A128GCM","kid":"Bob"} - -# And the sender computes the DH Z value as X25519(ephkey, recv_pub) -# (hex): - -# 4a 5d 9d 5b a4 ce 2d e1 72 8e 3b f4 80 35 0f 25 -# e0 7e 21 c9 47 d1 9e 33 76 f0 9b 3c 1e 16 17 42 - -# The receiver computes the DH Z value as X25519(seckey, ephkey_pub) -# (hex): - -# 4a 5d 9d 5b a4 ce 2d e1 72 8e 3b f4 80 35 0f 25 -# e0 7e 21 c9 47 d1 9e 33 76 f0 9b 3c 1e 16 17 42 - -# This is the same as the sender's value (both sides run this through -# the KDF before using it as a direct encryption key or AES128-KW key). - -# A.7. ECDH-ES with X448 - -# The public key to encrypt to (with a linebreak inserted for -# formatting reasons) is: - -# {"kty":"OKP","crv":"X448","kid":"Dave", -# "x":"PreoKbDNIPW8_AtZm2_sz22kYnEHvbDU80W0MCfYuXL8PjT7QjKhPKcG3LV67D2 -# uB73BxnvzNgk"} - -# The public key from the target key is (hex): - -# 3e b7 a8 29 b0 cd 20 f5 bc fc 0b 59 9b 6f ec cf -# 6d a4 62 71 07 bd b0 d4 f3 45 b4 30 27 d8 b9 72 -# fc 3e 34 fb 42 32 a1 3c a7 06 dc b5 7a ec 3d ae -# 07 bd c1 c6 7b f3 36 09 - -# The ephemeral secret happens to be (hex): - -# 9a 8f 49 25 d1 51 9f 57 75 cf 46 b0 4b 58 00 d4 -# ee 9e e8 ba e8 bc 55 65 d4 98 c2 8d d9 c9 ba f5 -# 74 a9 41 97 44 89 73 91 00 63 82 a6 f1 27 ab 1d -# 9a c2 d8 c0 a5 98 72 6b - - - -# Liusvaara Standards Track [Page 12] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# So the ephemeral public key is X448(ephkey, G) (hex): - -# 9b 08 f7 cc 31 b7 e3 e6 7d 22 d5 ae a1 21 07 4a -# 27 3b d2 b8 3d e0 9c 63 fa a7 3d 2c 22 c5 d9 bb -# c8 36 64 72 41 d9 53 d4 0c 5b 12 da 88 12 0d 53 -# 17 7f 80 e5 32 c4 1f a0 - -# This is packed into the ephemeral public key value (a linebreak -# inserted for formatting purposes): - -# {"kty":"OKP","crv":"X448", -# "x":"mwj3zDG34-Z9ItWuoSEHSic70rg94Jxj-qc9LCLF2bvINmRyQdlT1AxbEtqIEg1 -# TF3-A5TLEH6A"} - -# So the protected header could be, for example (a linebreak inserted -# for formatting purposes): - -# {"alg":"ECDH-ES+A256KW","epk":{"kty":"OKP","crv":"X448", -# "x":"mwj3zDG34-Z9ItWuoSEHSic70rg94Jxj-qc9LCLF2bvINmRyQdlT1AxbEtqIEg1 -# TF3-A5TLEH6A"},"enc":"A256GCM","kid":"Dave"} - -# And the sender computes the DH Z value as X448(ephkey,recv_pub) -# (hex): - -# 07 ff f4 18 1a c6 cc 95 ec 1c 16 a9 4a 0f 74 d1 -# 2d a2 32 ce 40 a7 75 52 28 1d 28 2b b6 0c 0b 56 -# fd 24 64 c3 35 54 39 36 52 1c 24 40 30 85 d5 9a -# 44 9a 50 37 51 4a 87 9d - -# The receiver computes the DH Z value as X448(seckey, ephkey_pub) -# (hex): - -# 07 ff f4 18 1a c6 cc 95 ec 1c 16 a9 4a 0f 74 d1 -# 2d a2 32 ce 40 a7 75 52 28 1d 28 2b b6 0c 0b 56 -# fd 24 64 c3 35 54 39 36 52 1c 24 40 30 85 d5 9a -# 44 9a 50 37 51 4a 87 9d - -# This is the same as the sender's value (both sides run this through -# KDF before using it as the direct encryption key or AES256-KW key). - - - - - - - - - - - - -# Liusvaara Standards Track [Page 13] - -# RFC 8037 CFRG ECDH and Signatures in JOSE January 2017 - - -# Acknowledgements - -# Thanks to Michael B. Jones for his comments on an initial draft of -# this document and editorial help. - -# Thanks to Matt Miller for some editorial help. - -# Author's Address - -# Ilari Liusvaara -# Independent - -# Email: ilariliusvaara@welho.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Liusvaara Standards Track [Page 14] diff --git a/tests/rfc/test_rfc8410.py b/tests/rfc/test_rfc8410.py deleted file mode 100644 index 9ad6ef9..0000000 --- a/tests/rfc/test_rfc8410.py +++ /dev/null @@ -1,2252 +0,0 @@ - -# Disable flake8 reporting -# flake8: noqa - -from jose.jws import verify - -expected_payload = b"It\xe2\x80\x99s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there\xe2\x80\x99s no knowing where you might be swept off to." - - -# [Docs] [txt|pdf] [draft-ietf-jose-c...] - - - -# Internet Engineering Task Force (IETF) S. Josefsson -# Request for Comments: 8410 SJD AB -# Category: Standards Track J. Schaad -# ISSN: 2070-1721 August Cellars -# August 2018 - - -# Algorithm Identifiers for Ed25519, Ed448, X25519, and X448 -# for Use in the Internet X.509 Public Key Infrastructure - -# Abstract - -# This document specifies algorithm identifiers and ASN.1 encoding -# formats for elliptic curve constructs using the curve25519 and -# curve448 curves. The signature algorithms covered are Ed25519 and -# Ed448. The key agreement algorithms covered are X25519 and X448. -# The encoding for public key, private key, and Edwards-curve Digital -# Signature Algorithm (EdDSA) structures is provided. - -# Status of This Memo - -# This is an Internet Standards Track document. - -# This document is a product of the Internet Engineering Task Force -# (IETF). It represents the consensus of the IETF community. It has -# received public review and has been approved for publication by the -# Internet Engineering Steering Group (IESG). Further information on -# Internet Standards is available in Section 2 of RFC 7841. - -# Information about the current status of this document, any errata, -# and how to provide feedback on it may be obtained at -# https://www.rfc-editor.org/info/rfc8410. - -# Copyright Notice - -# Copyright (c) 2018 IETF Trust and the persons identified as the -# document authors. All rights reserved. - -# This document is subject to BCP 78 and the IETF Trust's Legal -# Provisions Relating to IETF Documents -# (https://trustee.ietf.org/license-info) in effect on the date of -# publication of this document. Please review these documents -# carefully, as they describe your rights and restrictions with respect -# to this document. Code Components extracted from this document must -# include Simplified BSD License text as described in Section 4.e of -# the Trust Legal Provisions and are provided without warranty as -# described in the Simplified BSD License. - - - - -# Josefsson & Schaad Standards Track [Page 1] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Table of Contents - -# 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 -# 2. Requirements Terminology . . . . . . . . . . . . . . . . . . 3 -# 3. Curve25519 and Curve448 Algorithm Identifiers . . . . . . . . 3 -# 4. Subject Public Key Fields . . . . . . . . . . . . . . . . . . 4 -# 5. Key Usage Bits . . . . . . . . . . . . . . . . . . . . . . . 5 -# 6. EdDSA Signatures . . . . . . . . . . . . . . . . . . . . . . 6 -# 7. Private Key Format . . . . . . . . . . . . . . . . . . . . . 7 -# 8. Human-Readable Algorithm Names . . . . . . . . . . . . . . . 8 -# 9. ASN.1 Module . . . . . . . . . . . . . . . . . . . . . . . . 9 -# 10. Examples . . . . . . . . . . . . . . . . . . . . . . . . . . 11 -# 10.1. Example Ed25519 Public Key . . . . . . . . . . . . . . . 11 -# 10.2. Example X25519 Certificate . . . . . . . . . . . . . . . 12 -# 10.3. Examples of Ed25519 Private Key . . . . . . . . . . . . 14 -# 11. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 15 -# 12. Security Considerations . . . . . . . . . . . . . . . . . . . 15 -# 13. References . . . . . . . . . . . . . . . . . . . . . . . . . 16 -# 13.1. Normative References . . . . . . . . . . . . . . . . . . 16 -# 13.2. Informative References . . . . . . . . . . . . . . . . . 16 -# Appendix A. Invalid Encodings . . . . . . . . . . . . . . . . . 18 -# Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 19 -# Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 20 - -# 1. Introduction - -# In [RFC7748], the elliptic curves curve25519 and curve448 are -# described. They are designed with performance and security in mind. -# The curves may be used for Diffie-Hellman and digital signature -# operations. - -# [RFC7748] describes the operations on these curves for the Diffie- -# Hellman operation. A convention has developed that when these two -# curves are used with the Diffie-Hellman operation, they are referred -# to as X25519 and X448. This RFC defines the ASN.1 Object Identifiers -# (OIDs) for the operations X25519 and X448 along with the associated -# parameters. The use of these OIDs is described for public and -# private keys. - -# In [RFC8032] the elliptic curve signature system Edwards-curve -# Digital Signature Algorithm (EdDSA) is described along with a -# recommendation for the use of the curve25519 and curve448. EdDSA has -# defined two modes: the PureEdDSA mode without prehashing and the -# HashEdDSA mode with prehashing. The convention used for identifying -# the algorithm/curve combinations is to use "Ed25519" and "Ed448" for -# the PureEdDSA mode. This document does not provide the conventions - - - - - -# Josefsson & Schaad Standards Track [Page 2] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# needed for the prehash versions of the signature algorithm. The use -# of the OIDs is described for public keys, private keys and -# signatures. - -# [RFC8032] additionally defines the concept of a context. Contexts -# can be used to differentiate signatures generated for different -# purposes with the same key. The use of contexts is not defined in -# this document for the following reasons: - -# o The current implementations of Ed25519 do not support the use of -# contexts; thus, if specified, it will potentially delay the use of -# these algorithms further. - -# o EdDSA is the only IETF algorithm that currently supports the use -# of contexts; however, there is a possibility that there will be -# confusion between which algorithms need to have separate keys and -# which do not. This may result in a decrease of security for those -# other algorithms. - -# o There are still ongoing discussions among the cryptographic -# community about how effective the use of contexts is for -# preventing attacks. - -# o There needs to be discussions about the correct way to identify -# when context strings are to be used. It is not clear if different -# OIDs should be used for different contexts or the OID should -# merely note that a context string needs to be provided. - -# 2. Requirements Terminology - -# The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -# "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -# "OPTIONAL" in this document are to be interpreted as described in -# BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all -# capitals, as shown here. - -# 3. Curve25519 and Curve448 Algorithm Identifiers - -# Certificates conforming to [RFC5280] can convey a public key for any -# public key algorithm. The certificate indicates the algorithm -# through an algorithm identifier. An algorithm identifier consists of -# an OID and optional parameters. - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 3] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The AlgorithmIdentifier type, which is included for convenience, is -# defined as follows: - -# AlgorithmIdentifier ::= SEQUENCE { -# algorithm OBJECT IDENTIFIER, -# parameters ANY DEFINED BY algorithm OPTIONAL -# } - -# The fields in AlgorithmIdentifier have the following meanings: - -# o algorithm identifies the cryptographic algorithm with an object -# identifier. Four such OIDs are defined below. - -# o parameters, which are optional, are the associated parameters for -# the algorithm identifier in the algorithm field. - -# In this document, we define four new OIDs for identifying the -# different curve/algorithm pairs: the curves being curve25519 and -# curve448 and the algorithms being ECDH and EdDSA in pure mode. For -# all of the OIDs, the parameters MUST be absent. - -# It is possible to find systems that require the parameters to be -# present. This can be due to either a defect in the original 1997 -# syntax or a programming error where developers never got input where -# this was not true. The optimal solution is to fix these systems; -# where this is not possible, the problem needs to be restricted to -# that subsystem and not propagated to the Internet. - -# The same algorithm identifiers are used for identifying a public key, -# a private key, and a signature (for the two EdDSA related OIDs). -# Additional encoding information is provided below for each of these -# locations. - -# id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } -# id-X448 OBJECT IDENTIFIER ::= { 1 3 101 111 } -# id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } -# id-Ed448 OBJECT IDENTIFIER ::= { 1 3 101 113 } - -# 4. Subject Public Key Fields - -# In the X.509 certificate, the subjectPublicKeyInfo field has the -# SubjectPublicKeyInfo type, which has the following ASN.1 syntax: - -# SubjectPublicKeyInfo ::= SEQUENCE { -# algorithm AlgorithmIdentifier, -# subjectPublicKey BIT STRING -# } - - - - -# Josefsson & Schaad Standards Track [Page 4] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The fields in SubjectPublicKeyInfo have the following meanings: - -# o algorithm is the algorithm identifier and parameters for the -# public key (see above). - -# o subjectPublicKey contains the byte stream of the public key. The -# algorithms defined in this document always encode the public key -# as an exact multiple of 8 bits. - -# Both [RFC7748] and [RFC8032] define the public key value as being a -# byte string. It should be noted that the public key is computed -# differently for each of these documents; thus, the same private key -# will not produce the same public key. - -# The following is an example of a public key encoded using the textual -# encoding defined in [RFC7468]. - -# -----BEGIN PUBLIC KEY----- -# MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PUBLIC KEY----- - -# 5. Key Usage Bits - -# The intended application for the key is indicated in the keyUsage -# certificate extension. - -# If the keyUsage extension is present in a certificate that indicates -# id-X25519 or id-X448 in SubjectPublicKeyInfo, then the following MUST -# be present: - -# keyAgreement; - -# one of the following MAY also be present: - -# encipherOnly; or -# decipherOnly. - -# If the keyUsage extension is present in an end-entity certificate -# that indicates id-Ed25519 or id-Ed448, then the keyUsage extension -# MUST contain one or both of the following values: - -# nonRepudiation; and -# digitalSignature. - - - - - - - - -# Josefsson & Schaad Standards Track [Page 5] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# If the keyUsage extension is present in a certification authority -# certificate that indicates id-Ed25519 or id-Ed448, then the keyUsage -# extension MUST contain one or more of the following values: - -# nonRepudiation; -# digitalSignature; -# keyCertSign; and -# cRLSign. - -# 6. EdDSA Signatures - -# Signatures can be placed in a number of different ASN.1 structures. -# The top level structure for a certificate is given below as being -# illustrative of how signatures are frequently encoded with an -# algorithm identifier and a location for the signature. - -# Certificate ::= SEQUENCE { -# tbsCertificate TBSCertificate, -# signatureAlgorithm AlgorithmIdentifier, -# signatureValue BIT STRING } - -# The same algorithm identifiers are used for signatures as are used -# for public keys. When used to identify signature algorithms, the -# parameters MUST be absent. - -# The data to be signed is prepared for EdDSA. Then, a private key -# operation is performed to generate the signature value. This value -# is the opaque value ENC(R) || ENC(S) described in Section 3.3 of -# [RFC8032]. The octet string representing the signature is encoded -# directly in the BIT STRING without adding any additional ASN.1 -# wrapping. For the Certificate structure, the signature value is -# wrapped in the "signatureValue" BIT STRING field. - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 6] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 7. Private Key Format - -# "Asymmetric Key Packages" [RFC5958] describes how to encode a private -# key in a structure that both identifies what algorithm the private -# key is for and allows for the public key and additional attributes -# about the key to be included as well. For illustration, the ASN.1 -# structure OneAsymmetricKey is replicated below. The algorithm- -# specific details of how a private key is encoded are left for the -# document describing the algorithm itself. - -# OneAsymmetricKey ::= SEQUENCE { -# version Version, -# privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, -# privateKey PrivateKey, -# attributes [0] IMPLICIT Attributes OPTIONAL, -# ..., -# [[2: publicKey [1] IMPLICIT PublicKey OPTIONAL ]], -# ... -# } - -# PrivateKey ::= OCTET STRING - -# PublicKey ::= BIT STRING - -# For the keys defined in this document, the private key is always an -# opaque byte sequence. The ASN.1 type CurvePrivateKey is defined in -# this document to hold the byte sequence. Thus, when encoding a -# OneAsymmetricKey object, the private key is wrapped in a -# CurvePrivateKey object and wrapped by the OCTET STRING of the -# "privateKey" field. - -# CurvePrivateKey ::= OCTET STRING - -# To encode an EdDSA, X25519, or X448 private key, the "privateKey" -# field will hold the encoded private key. The "privateKeyAlgorithm" -# field uses the AlgorithmIdentifier structure. The structure is -# encoded as defined above. If present, the "publicKey" field will -# hold the encoded key as defined in [RFC7748] and [RFC8032]. - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 7] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The following is an example of a private key encoded using the -# textual encoding defined in [RFC7468]. - -# -----BEGIN PRIVATE KEY----- -# MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# -----END PRIVATE KEY----- - -# The following example, in addition to encoding the private key, has -# an attribute included as well as the public key. As with the prior -# example, the textual encoding defined in [RFC7468] is used. - -# -----BEGIN PRIVATE KEY----- -# MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB -# Z9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PRIVATE KEY------ - -# NOTE: There exist some private key import functions that have not -# picked up the new ASN.1 structure OneAsymmetricKey that is defined in -# [RFC7748]. This means that they will not accept a private key -# structure that contains the public key field. This means a balancing -# act needs to be done between being able to do a consistency check on -# the key pair and widest ability to import the key. - -# 8. Human-Readable Algorithm Names - -# For the purpose of consistent cross-implementation naming, this -# section establishes human-readable names for the algorithms specified -# in this document. Implementations SHOULD use these names when -# referring to the algorithms. If there is a strong reason to deviate -# from these names -- for example, if the implementation has a -# different naming convention and wants to maintain internal -# consistency -- it is encouraged to deviate as little as possible from -# the names given here. - -# Use the string "ECDH" when referring to a public key of type "X25519" -# or "X448" when the curve is not known or relevant. - -# When the curve is known, use the more specific string of "X25519" or -# "X448". - -# Use the string "EdDSA" when referring to a signing public key or -# signature when the curve is not known or relevant. - -# When the curve is known, use a more specific string. For the id- -# Ed25519 value use the string "Ed25519". For id-Ed448, use "Ed448". - - - - - -# Josefsson & Schaad Standards Track [Page 8] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 9. ASN.1 Module - -# For reference purposes, the ASN.1 syntax is presented as an ASN.1 -# module here. - -# -- ASN.1 Module - -# Safecurves-pkix-18 -# { iso(1) identified-organization(3) dod(6) internet(1) -# security(5) mechanisms(5) pkix(7) id-mod(0) -# id-mod-safecurves-pkix(93) } - -# DEFINITIONS EXPLICIT TAGS ::= -# BEGIN - -# IMPORTS -# SIGNATURE-ALGORITHM, KEY-AGREE, PUBLIC-KEY, KEY-WRAP, -# KeyUsage, AlgorithmIdentifier -# FROM AlgorithmInformation-2009 -# {iso(1) identified-organization(3) dod(6) internet(1) security(5) -# mechanisms(5) pkix(7) id-mod(0) -# id-mod-algorithmInformation-02(58)} - -# mda-sha512 -# FROM PKIX1-PSS-OAEP-Algorithms-2009 -# { iso(1) identified-organization(3) dod(6) internet(1) -# security(5) mechanisms(5) pkix(7) id-mod(0) -# id-mod-pkix1-rsa-pkalgs-02(54) } - -# kwa-aes128-wrap, kwa-aes256-wrap -# FROM CMSAesRsaesOaep-2009 -# { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) -# smime(16) modules(0) id-mod-cms-aes-02(38) } -# ; - - -# id-edwards-curve-algs OBJECT IDENTIFIER ::= { 1 3 101 } - -# id-X25519 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 110 } -# id-X448 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 111 } -# id-Ed25519 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 112 } -# id-Ed448 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 113 } - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 9] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# sa-Ed25519 SIGNATURE-ALGORITHM ::= { -# IDENTIFIER id-Ed25519 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-Ed25519} -# SMIME-CAPS { IDENTIFIED BY id-Ed25519 } -# } - -# pk-Ed25519 PUBLIC-KEY ::= { -# IDENTIFIER id-Ed25519 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE {digitalSignature, nonRepudiation, -# keyCertSign, cRLSign} -# PRIVATE-KEY CurvePrivateKey -# } - -# kaa-X25519 KEY-AGREE ::= { -# IDENTIFIER id-X25519 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-X25519} -# UKM -- TYPE no ASN.1 wrapping -- ARE preferredPresent -# SMIME-CAPS { -# TYPE AlgorithmIdentifier{KEY-WRAP, {KeyWrapAlgorithms}} -# IDENTIFIED BY id-X25519 } -# } - -# pk-X25519 PUBLIC-KEY ::= { -# IDENTIFIER id-X25519 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE { keyAgreement } -# PRIVATE-KEY CurvePrivateKey -# } - -# KeyWrapAlgorithms KEY-WRAP ::= { -# kwa-aes128-wrap | kwa-aes256-wrap, -# ... -# } - -# kaa-X448 KEY-AGREE ::= { -# IDENTIFIER id-X448 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-X448} -# UKM -- TYPE no ASN.1 wrapping -- ARE preferredPresent -# SMIME-CAPS { -# TYPE AlgorithmIdentifier{KEY-WRAP, {KeyWrapAlgorithms}} -# IDENTIFIED BY id-X448 } -# } - - - -# Josefsson & Schaad Standards Track [Page 10] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# pk-X448 PUBLIC-KEY ::= { -# IDENTIFIER id-X448 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE { keyAgreement } -# PRIVATE-KEY CurvePrivateKey -# } - -# CurvePrivateKey ::= OCTET STRING - - -# END - -# 10. Examples - -# This section contains illustrations of EdDSA public keys and -# certificates, illustrating parameter choices. - -# 10.1. Example Ed25519 Public Key - -# An example of an Ed25519 public key: - -# Public Key Information: -# Public Key Algorithm: Ed25519 -# Algorithm Security Level: High - -# Public Key Usage: - -# Public Key ID: 9b1f5eeded043385e4f7bc623c5975b90bc8bb3b - -# -----BEGIN PUBLIC KEY----- -# MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PUBLIC KEY----- - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 11] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 10.2. Example X25519 Certificate - -# An example of a self-issued PKIX certificate using Ed25519 to sign an -# X25519 public key would be: - -# 0 300: SEQUENCE { -# 4 223: SEQUENCE { -# 7 3: [0] { -# 9 1: INTEGER 2 -# : } -# 12 8: INTEGER 56 01 47 4A 2A 8D C3 30 -# 22 5: SEQUENCE { -# 24 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 29 25: SEQUENCE { -# 31 23: SET { -# 33 21: SEQUENCE { -# 35 3: OBJECT IDENTIFIER commonName (2 5 4 3) -# 40 14: UTF8String 'IETF Test Demo' -# : } -# : } -# : } -# 56 30: SEQUENCE { -# 58 13: UTCTime 01/08/2016 12:19:24 GMT -# 73 13: UTCTime 31/12/2040 23:59:59 GMT -# : } -# 88 25: SEQUENCE { -# 90 23: SET { -# 92 21: SEQUENCE { -# 94 3: OBJECT IDENTIFIER commonName (2 5 4 3) -# 99 14: UTF8String 'IETF Test Demo' -# : } -# : } -# : } -# 115 42: SEQUENCE { -# 117 5: SEQUENCE { -# 119 3: OBJECT IDENTIFIER -# : ECDH 25519 key agreement { 1 3 101 110 } -# : } -# 124 33: BIT STRING -# : 85 20 F0 09 89 30 A7 54 74 8B 7D DC B4 3E F7 5A -# : 0D BF 3A 0D 26 38 1A F4 EB A4 A9 8E AA 9B 4E 6A -# : } -# 159 69: [3] { -# 161 67: SEQUENCE { -# 163 15: SEQUENCE { -# 165 3: OBJECT IDENTIFIER basicConstraints (2 5 29 19) - - - -# Josefsson & Schaad Standards Track [Page 12] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 170 1: BOOLEAN TRUE -# 173 5: OCTET STRING, encapsulates { -# 175 3: SEQUENCE { -# 177 1: BOOLEAN FALSE -# : } -# : } -# : } -# 180 14: SEQUENCE { -# 182 3: OBJECT IDENTIFIER keyUsage (2 5 29 15) -# 187 1: BOOLEAN FALSE -# 190 4: OCTET STRING, encapsulates { -# 192 2: BIT STRING 3 unused bits -# : '10000'B (bit 4) -# : } -# : } -# 196 32: SEQUENCE { -# 198 3: OBJECT IDENTIFIER subjectKeyIdentifier (2 5 29 14) -# 203 1: BOOLEAN FALSE -# 206 22: OCTET STRING, encapsulates { -# 208 20: OCTET STRING -# : 9B 1F 5E ED ED 04 33 85 E4 F7 BC 62 3C 59 75 -# : B9 0B C8 BB 3B -# : } -# : } -# : } -# : } -# : } -# 230 5: SEQUENCE { -# 232 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 237 65: BIT STRING -# : AF 23 01 FE DD C9 E6 FF C1 CC A7 3D 74 D6 48 A4 -# : 39 80 82 CD DB 69 B1 4E 4D 06 EC F8 1A 25 CE 50 -# : D4 C2 C3 EB 74 6C 4E DD 83 46 85 6E C8 6F 3D CE -# : 1A 18 65 C5 7A C2 7B 50 A0 C3 50 07 F5 E7 D9 07 -# : } - -# -----BEGIN CERTIFICATE----- -# MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBUZX -# N0IERlbW8wHhcNMTYwODAxMTIxOTI0WhcNNDAxMjMxMjM1OTU5WjAZMRcwFQYDVQQD -# DA5JRVRGIFRlc3QgRGVtbzAqMAUGAytlbgMhAIUg8AmJMKdUdIt93LQ+91oNvzoNJj -# ga9OukqY6qm05qo0UwQzAPBgNVHRMBAf8EBTADAQEAMA4GA1UdDwEBAAQEAwIDCDAg -# BgNVHQ4BAQAEFgQUmx9e7e0EM4Xk97xiPFl1uQvIuzswBQYDK2VwA0EAryMB/t3J5v -# /BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg -# w1AH9efZBw== -# -----END CERTIFICATE----- - - - - -# Josefsson & Schaad Standards Track [Page 13] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 10.3. Examples of Ed25519 Private Key - -# An example of an Ed25519 private key without the public key: - -# -----BEGIN PRIVATE KEY----- -# MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# -----END PRIVATE KEY----- - -# The same item dumped as ASN.1 yields: - -# 0 30 46: SEQUENCE { -# 2 02 1: INTEGER 0 -# 5 30 5: SEQUENCE { -# 7 06 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 12 04 34: OCTET STRING -# : 04 20 D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 -# : F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 -# : 58 42 -# : } - -# Note that the value of the private key is: - -# D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 F8 AD -# 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 58 42 - -# An example of the same Ed25519 private key encoded with an attribute -# and the public key: - -# -----BEGIN PRIVATE KEY----- -# MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB -# Z9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PRIVATE KEY----- - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 14] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The same item dumped as ASN.1 yields: - -# 0 114: SEQUENCE { -# 2 1: INTEGER 1 -# 5 5: SEQUENCE { -# 7 3: OBJECT IDENTIFIER '1 3 101 112' -# : } -# 12 34: OCTET STRING, encapsulates { -# : 04 20 D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 -# : F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 -# : 58 42 -# : } -# 48 31: [0] { -# 50 29: SEQUENCE { -# 52 10: OBJECT IDENTIFIER '1 2 840 113549 1 9 9 20' -# 64 15: SET { -# 66 13: UTF8String 'Curdle Chairs' -# : } -# : } -# : } -# 81 33: [1] 00 19 BF 44 09 69 84 CD FE 85 41 BA C1 67 DC 3B -# 96 C8 50 86 AA 30 B6 B6 CB 0C 5C 38 AD 70 31 66 -# E1 -# : } - -# 11. IANA Considerations - -# For the ASN.1 module in Section 9, IANA has registered value 93 for -# "id-mod-safecurves-pkix" in the "SMI Security for PKIX Module -# Identifier" (1.3.6.1.5.5.7.0) registry. - -# The OIDs are being independently registered in the IANA registry "SMI -# Security for Cryptographic Algorithms" in [RFC8411]. - -# 12. Security Considerations - -# The security considerations of [RFC5280], [RFC7748], and [RFC8032] -# apply accordingly. - -# The procedures for going from a private key to a public key are -# different when used with Diffie-Hellman versus when used with Edwards -# Signatures. This means that the same public key cannot be used for -# both ECDH and EdDSA. - - - - - - - - -# Josefsson & Schaad Standards Track [Page 15] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 13. References - -# 13.1. Normative References - -# [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate -# Requirement Levels", BCP 14, RFC 2119, -# DOI 10.17487/RFC2119, March 1997, -# . - -# [RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., -# Housley, R., and W. Polk, "Internet X.509 Public Key -# Infrastructure Certificate and Certificate Revocation List -# (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008, -# . - -# [RFC5480] Turner, S., Brown, D., Yiu, K., Housley, R., and T. Polk, -# "Elliptic Curve Cryptography Subject Public Key -# Information", RFC 5480, DOI 10.17487/RFC5480, March 2009, -# . - -# [RFC5958] Turner, S., "Asymmetric Key Packages", RFC 5958, -# DOI 10.17487/RFC5958, August 2010, -# . - -# [RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves -# for Security", RFC 7748, DOI 10.17487/RFC7748, January -# 2016, . - -# [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital -# Signature Algorithm (EdDSA)", RFC 8032, -# DOI 10.17487/RFC8032, January 2017, -# . - -# [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC -# 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, -# May 2017, . - -# 13.2. Informative References - -# [RFC3279] Bassham, L., Polk, W., and R. Housley, "Algorithms and -# Identifiers for the Internet X.509 Public Key -# Infrastructure Certificate and Certificate Revocation List -# (CRL) Profile", RFC 3279, DOI 10.17487/RFC3279, April -# 2002, . - - - - - - - -# Josefsson & Schaad Standards Track [Page 16] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# [RFC4055] Schaad, J., Kaliski, B., and R. Housley, "Additional -# Algorithms and Identifiers for RSA Cryptography for use in -# the Internet X.509 Public Key Infrastructure Certificate -# and Certificate Revocation List (CRL) Profile", RFC 4055, -# DOI 10.17487/RFC4055, June 2005, -# . - -# [RFC5639] Lochter, M. and J. Merkle, "Elliptic Curve Cryptography -# (ECC) Brainpool Standard Curves and Curve Generation", -# RFC 5639, DOI 10.17487/RFC5639, March 2010, -# . - -# [RFC7468] Josefsson, S. and S. Leonard, "Textual Encodings of PKIX, -# PKCS, and CMS Structures", RFC 7468, DOI 10.17487/RFC7468, -# April 2015, . - -# [RFC8411] Schaad, J. and R. Andrews, "IANA Registration for the -# Cryptographic Algorithm Object Identifier Range", -# RFC 8411, DOI 10.17487/RFC8411, August 2018, -# . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 17] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Appendix A. Invalid Encodings - -# There are a number of things that need to be dealt with when a new -# key part is decoded and imported into the system. A partial list of -# these includes: - -# o ASN.1 encoding errors: Two items are highlighted here. First, the -# use of an OCTET STRING rather than a BIT STRING for the public -# key. The use of OCTET STRING was a copy error that existed in a -# previous draft version of this document; the structure is correct -# in [RFC5958]. However, any early implementation may have this -# wrong. Second, the value of the version field is required to be 0 -# if the publicKey is absent and 1 if present. This is called out -# in [RFC5958], but was not duplicated above. - -# o Key encoding errors: Both [RFC7748] and [RFC8032] have formatting -# requirements for keys that need to be enforced. In some cases, -# the enforcement is done at the time of importing, for example, -# doing masking or a mod p operation. In other cases, the -# enforcement is done by rejecting the keys and having an import -# failure. - -# o Key mismatch errors: If a public key is provided, it may not agree -# with the private key because either it is wrong or the wrong -# algorithm was used. - -# Some systems are also going to be stricter on what they accept. As -# stated in [RFC5958], BER decoding of OneAsymmetricKey objects is a -# requirement for compliance. Despite this requirement, some acceptors -# will only decode DER formats. The following is a BER encoding of a -# private key; it is valid, but it may not be accepted by many systems. - -# -----BEGIN PRIVATE KEY----- -# MIACAQAwgAYDK2VwAAAEIgQg1O5y2/kTWErVttjx92n4rTr+fCjL8dT74Jeoj0R1W -# EIAAA== -# -----END PRIVATE KEY----- - -# What follows here is a brief sampling of some incorrect keys. - -# In the following example, the private key does not match the masking -# requirements for X25519. For this example, the top bits are set to -# zero and the bottom three bits are set to 001. - -# -----BEGIN PRIVATE KEY----- -# MFMCAQEwBQYDK2VuBCIEIPj///////////////////////////////////////8/oS -# MDIQCEfA0sN1I082XmYJVRh6NzWg92E9FgnTpqTYxTrqpaIg== -# -----END PRIVATE KEY----- - - - - -# Josefsson & Schaad Standards Track [Page 18] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# In the following examples, the key is the wrong length because an -# all-zero byte has been removed. In one case, the first byte has been -# removed; in the other case, the last byte has been removed. - -# -----BEGIN PRIVATE KEY----- -# MFICAQEwBQYDK2VwBCIEIC3GfeUYbZGTAhwLEE2cbvJL7ivTlcy17VottfN6L8HwoS -# IDIADBfk2Lv/J8H7YYwj/OmIcDx++jzVkKrKwS0/HjyQyM -# -----END PRIVATE KEY----- - -# -----BEGIN PRIVATE KEY----- -# MFICAQEwBQYDK2VwBCIEILJXn1VaLqvausjUaZexwI/ozmOFjfEk78KcYN+7hsNJoS -# IDIACdQhJwzi/MCGcsQeQnIUh2JFybDxSrZxuLudJmpJLk -# -----END PRIVATE KEY----- - -# Acknowledgments - -# Text and/or inspiration were drawn from [RFC5280], [RFC3279], -# [RFC4055], [RFC5480], and [RFC5639]. - -# The following people discussed the document and provided feedback: -# Klaus Hartke, Ilari Liusvaara, Erwann Abalea, Rick Andrews, Rob -# Stradling, James Manger, Nikos Mavrogiannopoulos, Russ Housley, David -# Benjamin, Brian Smith, and Alex Wilson. - -# A big thank you to Symantec for kindly donating the OIDs used in this -# document. - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 19] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Authors' Addresses - -# Simon Josefsson -# SJD AB - -# Email: simon@josefsson.org - - -# Jim Schaad -# August Cellars - -# Email: ietf@augustcellars.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 20] - - - - - - - -# Internet Engineering Task Force (IETF) S. Josefsson -# Request for Comments: 8410 SJD AB -# Category: Standards Track J. Schaad -# ISSN: 2070-1721 August Cellars -# August 2018 - - -# Algorithm Identifiers for Ed25519, Ed448, X25519, and X448 -# for Use in the Internet X.509 Public Key Infrastructure - -# Abstract - -# This document specifies algorithm identifiers and ASN.1 encoding -# formats for elliptic curve constructs using the curve25519 and -# curve448 curves. The signature algorithms covered are Ed25519 and -# Ed448. The key agreement algorithms covered are X25519 and X448. -# The encoding for public key, private key, and Edwards-curve Digital -# Signature Algorithm (EdDSA) structures is provided. - -# Status of This Memo - -# This is an Internet Standards Track document. - -# This document is a product of the Internet Engineering Task Force -# (IETF). It represents the consensus of the IETF community. It has -# received public review and has been approved for publication by the -# Internet Engineering Steering Group (IESG). Further information on -# Internet Standards is available in Section 2 of RFC 7841. - -# Information about the current status of this document, any errata, -# and how to provide feedback on it may be obtained at -# https://www.rfc-editor.org/info/rfc8410. - -# Copyright Notice - -# Copyright (c) 2018 IETF Trust and the persons identified as the -# document authors. All rights reserved. - -# This document is subject to BCP 78 and the IETF Trust's Legal -# Provisions Relating to IETF Documents -# (https://trustee.ietf.org/license-info) in effect on the date of -# publication of this document. Please review these documents -# carefully, as they describe your rights and restrictions with respect -# to this document. Code Components extracted from this document must -# include Simplified BSD License text as described in Section 4.e of -# the Trust Legal Provisions and are provided without warranty as -# described in the Simplified BSD License. - - - - -# Josefsson & Schaad Standards Track [Page 1] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Table of Contents - -# 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 -# 2. Requirements Terminology . . . . . . . . . . . . . . . . . . 3 -# 3. Curve25519 and Curve448 Algorithm Identifiers . . . . . . . . 3 -# 4. Subject Public Key Fields . . . . . . . . . . . . . . . . . . 4 -# 5. Key Usage Bits . . . . . . . . . . . . . . . . . . . . . . . 5 -# 6. EdDSA Signatures . . . . . . . . . . . . . . . . . . . . . . 6 -# 7. Private Key Format . . . . . . . . . . . . . . . . . . . . . 7 -# 8. Human-Readable Algorithm Names . . . . . . . . . . . . . . . 8 -# 9. ASN.1 Module . . . . . . . . . . . . . . . . . . . . . . . . 9 -# 10. Examples . . . . . . . . . . . . . . . . . . . . . . . . . . 11 -# 10.1. Example Ed25519 Public Key . . . . . . . . . . . . . . . 11 -# 10.2. Example X25519 Certificate . . . . . . . . . . . . . . . 12 -# 10.3. Examples of Ed25519 Private Key . . . . . . . . . . . . 14 -# 11. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 15 -# 12. Security Considerations . . . . . . . . . . . . . . . . . . . 15 -# 13. References . . . . . . . . . . . . . . . . . . . . . . . . . 16 -# 13.1. Normative References . . . . . . . . . . . . . . . . . . 16 -# 13.2. Informative References . . . . . . . . . . . . . . . . . 16 -# Appendix A. Invalid Encodings . . . . . . . . . . . . . . . . . 18 -# Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . 19 -# Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 20 - -# 1. Introduction - -# In [RFC7748], the elliptic curves curve25519 and curve448 are -# described. They are designed with performance and security in mind. -# The curves may be used for Diffie-Hellman and digital signature -# operations. - -# [RFC7748] describes the operations on these curves for the Diffie- -# Hellman operation. A convention has developed that when these two -# curves are used with the Diffie-Hellman operation, they are referred -# to as X25519 and X448. This RFC defines the ASN.1 Object Identifiers -# (OIDs) for the operations X25519 and X448 along with the associated -# parameters. The use of these OIDs is described for public and -# private keys. - -# In [RFC8032] the elliptic curve signature system Edwards-curve -# Digital Signature Algorithm (EdDSA) is described along with a -# recommendation for the use of the curve25519 and curve448. EdDSA has -# defined two modes: the PureEdDSA mode without prehashing and the -# HashEdDSA mode with prehashing. The convention used for identifying -# the algorithm/curve combinations is to use "Ed25519" and "Ed448" for -# the PureEdDSA mode. This document does not provide the conventions - - - - - -# Josefsson & Schaad Standards Track [Page 2] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# needed for the prehash versions of the signature algorithm. The use -# of the OIDs is described for public keys, private keys and -# signatures. - -# [RFC8032] additionally defines the concept of a context. Contexts -# can be used to differentiate signatures generated for different -# purposes with the same key. The use of contexts is not defined in -# this document for the following reasons: - -# o The current implementations of Ed25519 do not support the use of -# contexts; thus, if specified, it will potentially delay the use of -# these algorithms further. - -# o EdDSA is the only IETF algorithm that currently supports the use -# of contexts; however, there is a possibility that there will be -# confusion between which algorithms need to have separate keys and -# which do not. This may result in a decrease of security for those -# other algorithms. - -# o There are still ongoing discussions among the cryptographic -# community about how effective the use of contexts is for -# preventing attacks. - -# o There needs to be discussions about the correct way to identify -# when context strings are to be used. It is not clear if different -# OIDs should be used for different contexts or the OID should -# merely note that a context string needs to be provided. - -# 2. Requirements Terminology - -# The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", -# "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and -# "OPTIONAL" in this document are to be interpreted as described in -# BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all -# capitals, as shown here. - -# 3. Curve25519 and Curve448 Algorithm Identifiers - -# Certificates conforming to [RFC5280] can convey a public key for any -# public key algorithm. The certificate indicates the algorithm -# through an algorithm identifier. An algorithm identifier consists of -# an OID and optional parameters. - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 3] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The AlgorithmIdentifier type, which is included for convenience, is -# defined as follows: - -# AlgorithmIdentifier ::= SEQUENCE { -# algorithm OBJECT IDENTIFIER, -# parameters ANY DEFINED BY algorithm OPTIONAL -# } - -# The fields in AlgorithmIdentifier have the following meanings: - -# o algorithm identifies the cryptographic algorithm with an object -# identifier. Four such OIDs are defined below. - -# o parameters, which are optional, are the associated parameters for -# the algorithm identifier in the algorithm field. - -# In this document, we define four new OIDs for identifying the -# different curve/algorithm pairs: the curves being curve25519 and -# curve448 and the algorithms being ECDH and EdDSA in pure mode. For -# all of the OIDs, the parameters MUST be absent. - -# It is possible to find systems that require the parameters to be -# present. This can be due to either a defect in the original 1997 -# syntax or a programming error where developers never got input where -# this was not true. The optimal solution is to fix these systems; -# where this is not possible, the problem needs to be restricted to -# that subsystem and not propagated to the Internet. - -# The same algorithm identifiers are used for identifying a public key, -# a private key, and a signature (for the two EdDSA related OIDs). -# Additional encoding information is provided below for each of these -# locations. - -# id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 } -# id-X448 OBJECT IDENTIFIER ::= { 1 3 101 111 } -# id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 } -# id-Ed448 OBJECT IDENTIFIER ::= { 1 3 101 113 } - -# 4. Subject Public Key Fields - -# In the X.509 certificate, the subjectPublicKeyInfo field has the -# SubjectPublicKeyInfo type, which has the following ASN.1 syntax: - -# SubjectPublicKeyInfo ::= SEQUENCE { -# algorithm AlgorithmIdentifier, -# subjectPublicKey BIT STRING -# } - - - - -# Josefsson & Schaad Standards Track [Page 4] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The fields in SubjectPublicKeyInfo have the following meanings: - -# o algorithm is the algorithm identifier and parameters for the -# public key (see above). - -# o subjectPublicKey contains the byte stream of the public key. The -# algorithms defined in this document always encode the public key -# as an exact multiple of 8 bits. - -# Both [RFC7748] and [RFC8032] define the public key value as being a -# byte string. It should be noted that the public key is computed -# differently for each of these documents; thus, the same private key -# will not produce the same public key. - -# The following is an example of a public key encoded using the textual -# encoding defined in [RFC7468]. - -# -----BEGIN PUBLIC KEY----- -# MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PUBLIC KEY----- - -# 5. Key Usage Bits - -# The intended application for the key is indicated in the keyUsage -# certificate extension. - -# If the keyUsage extension is present in a certificate that indicates -# id-X25519 or id-X448 in SubjectPublicKeyInfo, then the following MUST -# be present: - -# keyAgreement; - -# one of the following MAY also be present: - -# encipherOnly; or -# decipherOnly. - -# If the keyUsage extension is present in an end-entity certificate -# that indicates id-Ed25519 or id-Ed448, then the keyUsage extension -# MUST contain one or both of the following values: - -# nonRepudiation; and -# digitalSignature. - - - - - - - - -# Josefsson & Schaad Standards Track [Page 5] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# If the keyUsage extension is present in a certification authority -# certificate that indicates id-Ed25519 or id-Ed448, then the keyUsage -# extension MUST contain one or more of the following values: - -# nonRepudiation; -# digitalSignature; -# keyCertSign; and -# cRLSign. - -# 6. EdDSA Signatures - -# Signatures can be placed in a number of different ASN.1 structures. -# The top level structure for a certificate is given below as being -# illustrative of how signatures are frequently encoded with an -# algorithm identifier and a location for the signature. - -# Certificate ::= SEQUENCE { -# tbsCertificate TBSCertificate, -# signatureAlgorithm AlgorithmIdentifier, -# signatureValue BIT STRING } - -# The same algorithm identifiers are used for signatures as are used -# for public keys. When used to identify signature algorithms, the -# parameters MUST be absent. - -# The data to be signed is prepared for EdDSA. Then, a private key -# operation is performed to generate the signature value. This value -# is the opaque value ENC(R) || ENC(S) described in Section 3.3 of -# [RFC8032]. The octet string representing the signature is encoded -# directly in the BIT STRING without adding any additional ASN.1 -# wrapping. For the Certificate structure, the signature value is -# wrapped in the "signatureValue" BIT STRING field. - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 6] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 7. Private Key Format - -# "Asymmetric Key Packages" [RFC5958] describes how to encode a private -# key in a structure that both identifies what algorithm the private -# key is for and allows for the public key and additional attributes -# about the key to be included as well. For illustration, the ASN.1 -# structure OneAsymmetricKey is replicated below. The algorithm- -# specific details of how a private key is encoded are left for the -# document describing the algorithm itself. - -# OneAsymmetricKey ::= SEQUENCE { -# version Version, -# privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, -# privateKey PrivateKey, -# attributes [0] IMPLICIT Attributes OPTIONAL, -# ..., -# [[2: publicKey [1] IMPLICIT PublicKey OPTIONAL ]], -# ... -# } - -# PrivateKey ::= OCTET STRING - -# PublicKey ::= BIT STRING - -# For the keys defined in this document, the private key is always an -# opaque byte sequence. The ASN.1 type CurvePrivateKey is defined in -# this document to hold the byte sequence. Thus, when encoding a -# OneAsymmetricKey object, the private key is wrapped in a -# CurvePrivateKey object and wrapped by the OCTET STRING of the -# "privateKey" field. - -# CurvePrivateKey ::= OCTET STRING - -# To encode an EdDSA, X25519, or X448 private key, the "privateKey" -# field will hold the encoded private key. The "privateKeyAlgorithm" -# field uses the AlgorithmIdentifier structure. The structure is -# encoded as defined above. If present, the "publicKey" field will -# hold the encoded key as defined in [RFC7748] and [RFC8032]. - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 7] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The following is an example of a private key encoded using the -# textual encoding defined in [RFC7468]. - -# -----BEGIN PRIVATE KEY----- -# MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# -----END PRIVATE KEY----- - -# The following example, in addition to encoding the private key, has -# an attribute included as well as the public key. As with the prior -# example, the textual encoding defined in [RFC7468] is used. - -# -----BEGIN PRIVATE KEY----- -# MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB -# Z9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PRIVATE KEY------ - -# NOTE: There exist some private key import functions that have not -# picked up the new ASN.1 structure OneAsymmetricKey that is defined in -# [RFC7748]. This means that they will not accept a private key -# structure that contains the public key field. This means a balancing -# act needs to be done between being able to do a consistency check on -# the key pair and widest ability to import the key. - -# 8. Human-Readable Algorithm Names - -# For the purpose of consistent cross-implementation naming, this -# section establishes human-readable names for the algorithms specified -# in this document. Implementations SHOULD use these names when -# referring to the algorithms. If there is a strong reason to deviate -# from these names -- for example, if the implementation has a -# different naming convention and wants to maintain internal -# consistency -- it is encouraged to deviate as little as possible from -# the names given here. - -# Use the string "ECDH" when referring to a public key of type "X25519" -# or "X448" when the curve is not known or relevant. - -# When the curve is known, use the more specific string of "X25519" or -# "X448". - -# Use the string "EdDSA" when referring to a signing public key or -# signature when the curve is not known or relevant. - -# When the curve is known, use a more specific string. For the id- -# Ed25519 value use the string "Ed25519". For id-Ed448, use "Ed448". - - - - - -# Josefsson & Schaad Standards Track [Page 8] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 9. ASN.1 Module - -# For reference purposes, the ASN.1 syntax is presented as an ASN.1 -# module here. - -# -- ASN.1 Module - -# Safecurves-pkix-18 -# { iso(1) identified-organization(3) dod(6) internet(1) -# security(5) mechanisms(5) pkix(7) id-mod(0) -# id-mod-safecurves-pkix(93) } - -# DEFINITIONS EXPLICIT TAGS ::= -# BEGIN - -# IMPORTS -# SIGNATURE-ALGORITHM, KEY-AGREE, PUBLIC-KEY, KEY-WRAP, -# KeyUsage, AlgorithmIdentifier -# FROM AlgorithmInformation-2009 -# {iso(1) identified-organization(3) dod(6) internet(1) security(5) -# mechanisms(5) pkix(7) id-mod(0) -# id-mod-algorithmInformation-02(58)} - -# mda-sha512 -# FROM PKIX1-PSS-OAEP-Algorithms-2009 -# { iso(1) identified-organization(3) dod(6) internet(1) -# security(5) mechanisms(5) pkix(7) id-mod(0) -# id-mod-pkix1-rsa-pkalgs-02(54) } - -# kwa-aes128-wrap, kwa-aes256-wrap -# FROM CMSAesRsaesOaep-2009 -# { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) -# smime(16) modules(0) id-mod-cms-aes-02(38) } -# ; - - -# id-edwards-curve-algs OBJECT IDENTIFIER ::= { 1 3 101 } - -# id-X25519 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 110 } -# id-X448 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 111 } -# id-Ed25519 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 112 } -# id-Ed448 OBJECT IDENTIFIER ::= { id-edwards-curve-algs 113 } - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 9] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# sa-Ed25519 SIGNATURE-ALGORITHM ::= { -# IDENTIFIER id-Ed25519 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-Ed25519} -# SMIME-CAPS { IDENTIFIED BY id-Ed25519 } -# } - -# pk-Ed25519 PUBLIC-KEY ::= { -# IDENTIFIER id-Ed25519 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE {digitalSignature, nonRepudiation, -# keyCertSign, cRLSign} -# PRIVATE-KEY CurvePrivateKey -# } - -# kaa-X25519 KEY-AGREE ::= { -# IDENTIFIER id-X25519 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-X25519} -# UKM -- TYPE no ASN.1 wrapping -- ARE preferredPresent -# SMIME-CAPS { -# TYPE AlgorithmIdentifier{KEY-WRAP, {KeyWrapAlgorithms}} -# IDENTIFIED BY id-X25519 } -# } - -# pk-X25519 PUBLIC-KEY ::= { -# IDENTIFIER id-X25519 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE { keyAgreement } -# PRIVATE-KEY CurvePrivateKey -# } - -# KeyWrapAlgorithms KEY-WRAP ::= { -# kwa-aes128-wrap | kwa-aes256-wrap, -# ... -# } - -# kaa-X448 KEY-AGREE ::= { -# IDENTIFIER id-X448 -# PARAMS ARE absent -# PUBLIC-KEYS {pk-X448} -# UKM -- TYPE no ASN.1 wrapping -- ARE preferredPresent -# SMIME-CAPS { -# TYPE AlgorithmIdentifier{KEY-WRAP, {KeyWrapAlgorithms}} -# IDENTIFIED BY id-X448 } -# } - - - -# Josefsson & Schaad Standards Track [Page 10] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# pk-X448 PUBLIC-KEY ::= { -# IDENTIFIER id-X448 -# -- KEY no ASN.1 wrapping -- -# PARAMS ARE absent -# CERT-KEY-USAGE { keyAgreement } -# PRIVATE-KEY CurvePrivateKey -# } - -# CurvePrivateKey ::= OCTET STRING - - -# END - -# 10. Examples - -# This section contains illustrations of EdDSA public keys and -# certificates, illustrating parameter choices. - -# 10.1. Example Ed25519 Public Key - -# An example of an Ed25519 public key: - -# Public Key Information: -# Public Key Algorithm: Ed25519 -# Algorithm Security Level: High - -# Public Key Usage: - -# Public Key ID: 9b1f5eeded043385e4f7bc623c5975b90bc8bb3b - -# -----BEGIN PUBLIC KEY----- -# MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PUBLIC KEY----- - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 11] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 10.2. Example X25519 Certificate - -# An example of a self-issued PKIX certificate using Ed25519 to sign an -# X25519 public key would be: - -# 0 300: SEQUENCE { -# 4 223: SEQUENCE { -# 7 3: [0] { -# 9 1: INTEGER 2 -# : } -# 12 8: INTEGER 56 01 47 4A 2A 8D C3 30 -# 22 5: SEQUENCE { -# 24 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 29 25: SEQUENCE { -# 31 23: SET { -# 33 21: SEQUENCE { -# 35 3: OBJECT IDENTIFIER commonName (2 5 4 3) -# 40 14: UTF8String 'IETF Test Demo' -# : } -# : } -# : } -# 56 30: SEQUENCE { -# 58 13: UTCTime 01/08/2016 12:19:24 GMT -# 73 13: UTCTime 31/12/2040 23:59:59 GMT -# : } -# 88 25: SEQUENCE { -# 90 23: SET { -# 92 21: SEQUENCE { -# 94 3: OBJECT IDENTIFIER commonName (2 5 4 3) -# 99 14: UTF8String 'IETF Test Demo' -# : } -# : } -# : } -# 115 42: SEQUENCE { -# 117 5: SEQUENCE { -# 119 3: OBJECT IDENTIFIER -# : ECDH 25519 key agreement { 1 3 101 110 } -# : } -# 124 33: BIT STRING -# : 85 20 F0 09 89 30 A7 54 74 8B 7D DC B4 3E F7 5A -# : 0D BF 3A 0D 26 38 1A F4 EB A4 A9 8E AA 9B 4E 6A -# : } -# 159 69: [3] { -# 161 67: SEQUENCE { -# 163 15: SEQUENCE { -# 165 3: OBJECT IDENTIFIER basicConstraints (2 5 29 19) - - - -# Josefsson & Schaad Standards Track [Page 12] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 170 1: BOOLEAN TRUE -# 173 5: OCTET STRING, encapsulates { -# 175 3: SEQUENCE { -# 177 1: BOOLEAN FALSE -# : } -# : } -# : } -# 180 14: SEQUENCE { -# 182 3: OBJECT IDENTIFIER keyUsage (2 5 29 15) -# 187 1: BOOLEAN FALSE -# 190 4: OCTET STRING, encapsulates { -# 192 2: BIT STRING 3 unused bits -# : '10000'B (bit 4) -# : } -# : } -# 196 32: SEQUENCE { -# 198 3: OBJECT IDENTIFIER subjectKeyIdentifier (2 5 29 14) -# 203 1: BOOLEAN FALSE -# 206 22: OCTET STRING, encapsulates { -# 208 20: OCTET STRING -# : 9B 1F 5E ED ED 04 33 85 E4 F7 BC 62 3C 59 75 -# : B9 0B C8 BB 3B -# : } -# : } -# : } -# : } -# : } -# 230 5: SEQUENCE { -# 232 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 237 65: BIT STRING -# : AF 23 01 FE DD C9 E6 FF C1 CC A7 3D 74 D6 48 A4 -# : 39 80 82 CD DB 69 B1 4E 4D 06 EC F8 1A 25 CE 50 -# : D4 C2 C3 EB 74 6C 4E DD 83 46 85 6E C8 6F 3D CE -# : 1A 18 65 C5 7A C2 7B 50 A0 C3 50 07 F5 E7 D9 07 -# : } - -# -----BEGIN CERTIFICATE----- -# MIIBLDCB36ADAgECAghWAUdKKo3DMDAFBgMrZXAwGTEXMBUGA1UEAwwOSUVURiBUZX -# N0IERlbW8wHhcNMTYwODAxMTIxOTI0WhcNNDAxMjMxMjM1OTU5WjAZMRcwFQYDVQQD -# DA5JRVRGIFRlc3QgRGVtbzAqMAUGAytlbgMhAIUg8AmJMKdUdIt93LQ+91oNvzoNJj -# ga9OukqY6qm05qo0UwQzAPBgNVHRMBAf8EBTADAQEAMA4GA1UdDwEBAAQEAwIDCDAg -# BgNVHQ4BAQAEFgQUmx9e7e0EM4Xk97xiPFl1uQvIuzswBQYDK2VwA0EAryMB/t3J5v -# /BzKc9dNZIpDmAgs3babFOTQbs+BolzlDUwsPrdGxO3YNGhW7Ibz3OGhhlxXrCe1Cg -# w1AH9efZBw== -# -----END CERTIFICATE----- - - - - -# Josefsson & Schaad Standards Track [Page 13] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 10.3. Examples of Ed25519 Private Key - -# An example of an Ed25519 private key without the public key: - -# -----BEGIN PRIVATE KEY----- -# MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# -----END PRIVATE KEY----- - -# The same item dumped as ASN.1 yields: - -# 0 30 46: SEQUENCE { -# 2 02 1: INTEGER 0 -# 5 30 5: SEQUENCE { -# 7 06 3: OBJECT IDENTIFIER -# : Ed 25519 signature algorithm { 1 3 101 112 } -# : } -# 12 04 34: OCTET STRING -# : 04 20 D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 -# : F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 -# : 58 42 -# : } - -# Note that the value of the private key is: - -# D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 F8 AD -# 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 58 42 - -# An example of the same Ed25519 private key encoded with an attribute -# and the public key: - -# -----BEGIN PRIVATE KEY----- -# MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC -# oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB -# Z9w7lshQhqowtrbLDFw4rXAxZuE= -# -----END PRIVATE KEY----- - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 14] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# The same item dumped as ASN.1 yields: - -# 0 114: SEQUENCE { -# 2 1: INTEGER 1 -# 5 5: SEQUENCE { -# 7 3: OBJECT IDENTIFIER '1 3 101 112' -# : } -# 12 34: OCTET STRING, encapsulates { -# : 04 20 D4 EE 72 DB F9 13 58 4A D5 B6 D8 F1 F7 69 -# : F8 AD 3A FE 7C 28 CB F1 D4 FB E0 97 A8 8F 44 75 -# : 58 42 -# : } -# 48 31: [0] { -# 50 29: SEQUENCE { -# 52 10: OBJECT IDENTIFIER '1 2 840 113549 1 9 9 20' -# 64 15: SET { -# 66 13: UTF8String 'Curdle Chairs' -# : } -# : } -# : } -# 81 33: [1] 00 19 BF 44 09 69 84 CD FE 85 41 BA C1 67 DC 3B -# 96 C8 50 86 AA 30 B6 B6 CB 0C 5C 38 AD 70 31 66 -# E1 -# : } - -# 11. IANA Considerations - -# For the ASN.1 module in Section 9, IANA has registered value 93 for -# "id-mod-safecurves-pkix" in the "SMI Security for PKIX Module -# Identifier" (1.3.6.1.5.5.7.0) registry. - -# The OIDs are being independently registered in the IANA registry "SMI -# Security for Cryptographic Algorithms" in [RFC8411]. - -# 12. Security Considerations - -# The security considerations of [RFC5280], [RFC7748], and [RFC8032] -# apply accordingly. - -# The procedures for going from a private key to a public key are -# different when used with Diffie-Hellman versus when used with Edwards -# Signatures. This means that the same public key cannot be used for -# both ECDH and EdDSA. - - - - - - - - -# Josefsson & Schaad Standards Track [Page 15] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# 13. References - -# 13.1. Normative References - -# [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate -# Requirement Levels", BCP 14, RFC 2119, -# DOI 10.17487/RFC2119, March 1997, -# . - -# [RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., -# Housley, R., and W. Polk, "Internet X.509 Public Key -# Infrastructure Certificate and Certificate Revocation List -# (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008, -# . - -# [RFC5480] Turner, S., Brown, D., Yiu, K., Housley, R., and T. Polk, -# "Elliptic Curve Cryptography Subject Public Key -# Information", RFC 5480, DOI 10.17487/RFC5480, March 2009, -# . - -# [RFC5958] Turner, S., "Asymmetric Key Packages", RFC 5958, -# DOI 10.17487/RFC5958, August 2010, -# . - -# [RFC7748] Langley, A., Hamburg, M., and S. Turner, "Elliptic Curves -# for Security", RFC 7748, DOI 10.17487/RFC7748, January -# 2016, . - -# [RFC8032] Josefsson, S. and I. Liusvaara, "Edwards-Curve Digital -# Signature Algorithm (EdDSA)", RFC 8032, -# DOI 10.17487/RFC8032, January 2017, -# . - -# [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC -# 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, -# May 2017, . - -# 13.2. Informative References - -# [RFC3279] Bassham, L., Polk, W., and R. Housley, "Algorithms and -# Identifiers for the Internet X.509 Public Key -# Infrastructure Certificate and Certificate Revocation List -# (CRL) Profile", RFC 3279, DOI 10.17487/RFC3279, April -# 2002, . - - - - - - - -# Josefsson & Schaad Standards Track [Page 16] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# [RFC4055] Schaad, J., Kaliski, B., and R. Housley, "Additional -# Algorithms and Identifiers for RSA Cryptography for use in -# the Internet X.509 Public Key Infrastructure Certificate -# and Certificate Revocation List (CRL) Profile", RFC 4055, -# DOI 10.17487/RFC4055, June 2005, -# . - -# [RFC5639] Lochter, M. and J. Merkle, "Elliptic Curve Cryptography -# (ECC) Brainpool Standard Curves and Curve Generation", -# RFC 5639, DOI 10.17487/RFC5639, March 2010, -# . - -# [RFC7468] Josefsson, S. and S. Leonard, "Textual Encodings of PKIX, -# PKCS, and CMS Structures", RFC 7468, DOI 10.17487/RFC7468, -# April 2015, . - -# [RFC8411] Schaad, J. and R. Andrews, "IANA Registration for the -# Cryptographic Algorithm Object Identifier Range", -# RFC 8411, DOI 10.17487/RFC8411, August 2018, -# . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 17] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Appendix A. Invalid Encodings - -# There are a number of things that need to be dealt with when a new -# key part is decoded and imported into the system. A partial list of -# these includes: - -# o ASN.1 encoding errors: Two items are highlighted here. First, the -# use of an OCTET STRING rather than a BIT STRING for the public -# key. The use of OCTET STRING was a copy error that existed in a -# previous draft version of this document; the structure is correct -# in [RFC5958]. However, any early implementation may have this -# wrong. Second, the value of the version field is required to be 0 -# if the publicKey is absent and 1 if present. This is called out -# in [RFC5958], but was not duplicated above. - -# o Key encoding errors: Both [RFC7748] and [RFC8032] have formatting -# requirements for keys that need to be enforced. In some cases, -# the enforcement is done at the time of importing, for example, -# doing masking or a mod p operation. In other cases, the -# enforcement is done by rejecting the keys and having an import -# failure. - -# o Key mismatch errors: If a public key is provided, it may not agree -# with the private key because either it is wrong or the wrong -# algorithm was used. - -# Some systems are also going to be stricter on what they accept. As -# stated in [RFC5958], BER decoding of OneAsymmetricKey objects is a -# requirement for compliance. Despite this requirement, some acceptors -# will only decode DER formats. The following is a BER encoding of a -# private key; it is valid, but it may not be accepted by many systems. - -# -----BEGIN PRIVATE KEY----- -# MIACAQAwgAYDK2VwAAAEIgQg1O5y2/kTWErVttjx92n4rTr+fCjL8dT74Jeoj0R1W -# EIAAA== -# -----END PRIVATE KEY----- - -# What follows here is a brief sampling of some incorrect keys. - -# In the following example, the private key does not match the masking -# requirements for X25519. For this example, the top bits are set to -# zero and the bottom three bits are set to 001. - -# -----BEGIN PRIVATE KEY----- -# MFMCAQEwBQYDK2VuBCIEIPj///////////////////////////////////////8/oS -# MDIQCEfA0sN1I082XmYJVRh6NzWg92E9FgnTpqTYxTrqpaIg== -# -----END PRIVATE KEY----- - - - - -# Josefsson & Schaad Standards Track [Page 18] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# In the following examples, the key is the wrong length because an -# all-zero byte has been removed. In one case, the first byte has been -# removed; in the other case, the last byte has been removed. - -# -----BEGIN PRIVATE KEY----- -# MFICAQEwBQYDK2VwBCIEIC3GfeUYbZGTAhwLEE2cbvJL7ivTlcy17VottfN6L8HwoS -# IDIADBfk2Lv/J8H7YYwj/OmIcDx++jzVkKrKwS0/HjyQyM -# -----END PRIVATE KEY----- - -# -----BEGIN PRIVATE KEY----- -# MFICAQEwBQYDK2VwBCIEILJXn1VaLqvausjUaZexwI/ozmOFjfEk78KcYN+7hsNJoS -# IDIACdQhJwzi/MCGcsQeQnIUh2JFybDxSrZxuLudJmpJLk -# -----END PRIVATE KEY----- - -# Acknowledgments - -# Text and/or inspiration were drawn from [RFC5280], [RFC3279], -# [RFC4055], [RFC5480], and [RFC5639]. - -# The following people discussed the document and provided feedback: -# Klaus Hartke, Ilari Liusvaara, Erwann Abalea, Rick Andrews, Rob -# Stradling, James Manger, Nikos Mavrogiannopoulos, Russ Housley, David -# Benjamin, Brian Smith, and Alex Wilson. - -# A big thank you to Symantec for kindly donating the OIDs used in this -# document. - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 19] - -# RFC 8410 Safe Curves for X.509 August 2018 - - -# Authors' Addresses - -# Simon Josefsson -# SJD AB - -# Email: simon@josefsson.org - - -# Jim Schaad -# August Cellars - -# Email: ietf@augustcellars.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# Josefsson & Schaad Standards Track [Page 20] diff --git a/tests/test_asn1.py b/tests/test_asn1.py index 4167ea9..64f2d4b 100644 --- a/tests/test_asn1.py +++ b/tests/test_asn1.py @@ -1,167 +1,167 @@ """Tests for ``jose.backends._asn1``.""" import base64 import pytest try: from jose.backends import _asn1 except ImportError: _asn1 = None pytestmark = [ pytest.mark.pycrypto, pytest.mark.pycryptodome, - pytest.mark.skipif(_asn1 is None, reason="ASN1 backend not available") + pytest.mark.skipif(_asn1 is None, reason="ASN1 backend not available"), ] PKCS1_PRIVATE_KEY = b"""MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPp X8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggA lS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r1 7/cYTp76brKpU1I4kM20M//dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XH Gz69Q7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5a ynQpwaVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v8 2LneTdsqA25Wi2Ld/c0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp 8uppGF02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQ v61VogqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtf k5rn7iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72 Sust9Hnd9B7V35YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6Ns Rk3jNdVMCAwEAAQKCAgEArx+0JXigDHtFZr4pYEPjwMgCBJ2dr8+L8PptB/4g+LoK 9MKqR7M4aTO+PoILPXPyWvZq/meeDakyZLrcdc8ad1ArKF7baDBpeGEbkRA9JfV5H jNq/ea4gyvDMCGou8ZPSQCnkRmr8LFQbJDgnM5Za5AYrwEv2aEh67IrTHq53W83rM ioIumCNiG+7TQ7egEGiYsQ745GLrECLZhKKRTgt/T+k1cSk1LLJawme5XgJUw+3D9 GddJEepvYoL+wZ/gnO2ADyPnPdQ7oc2NPcFMXpmIQf29+/g7FflatfQhkIv+eC6bB 51DhdMi1zyp2hOhzKg6jn74ixVX+Hts2/cMiAPu0NaWmU9n8g7HmXWc4+uSO/fssG jI3DLYKd5xnhrq4a3ZO5oJLeMO9U71+Ykctg23PTHwNAGrsPYdjGcBnJEdtbXa31a gI5PAG6rgGUY3iSoWqHLgBTxrX04TWVvLQi8wbxh7BEF0yasOeZKxdE2IWYg75zGs jluyHlOnpRa5lSf6KZ6thh9eczFHYtS4DvYBcZ9hZW/g87ie28SkBFxxl0brYt9uK NYJvuajVG8kT80AC7Wzg2q7Wmnoww3JNJUbNths5dqKyUSlMFMIB/vOePFHLrA6qD fAnsQHgUb9WHhUrYsH20XKpqR2OjmWU05bV4pSMW/JwG37o+px1yKECggEBANnwx0 d7ksEMvJjeN5plDy3eMLifBI+6SL/o5TXDoFM6rJxF+0UP70uouYJq2dI+DCSA6c/ Esn7WAOirY177adKcBV8biwAtmKHnFnCs/kwAZq8lMvQPtNPJ/vq2n40kO48h8fxb eGcmyAqFPZ4YKSxrPA4cdbHIuFSt9WyaUcVFmzdTFHVlRP70EXdmXHt84byWNB4CH eq8zmrNxPNAi65nEkUks7iBQMtuvyV2+aXjDOTBMCd66IhIh2iZq1O7kXUwgh1OH9 hCa7oriHyAdgkKdKCWocmbPPENOETgjraA9wRIXwOYTDb1X5hMvi1mCHo8xjMju4s zD03xJVi7WrsCggEBANTEblCkxEyhJqaMZF3U3df2Yr/ZtHqsrTr4lwB/MOKkzmuS rROxheEkKIsxbiV+AxTvtPR1FQrlqbhTJRwy+pw4KPJ7P4fq2R/YBqvXSNBCamTt6 l2XdXqnAk3A++cOEZ2lU9ubfgdeN2Ih8rgdn1LWeOSjCWfExmkoU61/Xe6xAMeXKQ SlHKSnX9voxuE2xINHeU6ZAKy1kGmrJtEiWnI8b8C4s8fTyDtXJ1Lasys0iHO2Tz2 jUhf4IJwb87Lk7Ize2MrI+oPzVDXlmkbjkB4tYyoiRTj8rk8pwBW/HVv002pjOLTa 4kz1kQ3lsZ/3As4zfNi7mWEhadmEsAIfYkkCggEBANO39r/Yqj5kUyrmZXnVxyM2A Hq58EJ4I4hbhZ/vRWbVTy4ZRfpXeo4zgNPTXXvCzyT/HyS53vUcjJF7PfPdpXX2H7 m/Fg+8O9S8m64mQHwwv5BSQOecAnzkdJG2q9T/Z+Sqg1w2uAbtQ9QEkFFvA0ClhBf pSeTGK1wICq3QVLOh5SGf0fYhxR8wl284v4svTFRaTpMAV3Pcq2JSN4xgHdH1S2hk OTt6RSnbklGg/PFMWxA3JMKVwiPy4aiZ8DhNtQb1ctFpPcJm9CRNejAI06IAyD/hV ZZ2+oLp5snypHFjY5SDgdoKL7AMOyvHEdEkmAO32ot/oQefOLTtGOzURVUCggEBAL Sx5iYi6HtT2SlUzeBKaeWBYDgiwf31LGGKwWMwoem5oX0GYmr5NwQP20brQeohbKi ZMwrxbF+G0G60Xi3mtaN6pnvYZAogTymWI4RJH5OO9CCnVYUKnkD+GRzDqqt97UP/ Joq5MX08bLiwsBvhPG/zqVQzikdQfFjOYNJV+wY92LWpELLbLso/Q0/WDyExjA8Z4 lH36vTCddTn/91Y2Ytu/FGmCzjICaMrzz+0cLlesgvjZsSoMY4dskQiEQN7G9I/Z8 pAiVEKlBf52N4fYUPfs/oShMty/O5KPNG7L0nrUKlnfr9JrStC2l/9FK8P7pgEbiD 6obY11FlhMMF8udECggEBAIKhvOFtipD1jqDOpjOoR9sK/lRR5bVVWQfamMDN1Awm jJbVHS8hhtYUM/4sh2p12P6RgoO8fODf1vEcWFh3xxNZE1pPCPaICD9i5U+NRvPz2 vC900HcraLRrUFaRzwhqOOknYJSBrGzW+Cx3YSeaOCgnKyI8B5gw4C0G0iL1dSsz2 bR1O4GNOVfT3R6joZEXATFo/Kc2L0YAvApBNUYvY0kbjJ/JfTO5060SsWftf4iw3j rhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGRmdUxHwi1ulkspAn/fmY7f0hZ pskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8=""" PKCS8_PRIVATE_KEY = b"""MIIJRQIBADANBgkqhkiG9w0BAQEFAASCCS8wggkrAg EAAoICAQC1Ip9J4jR+6REhffwAeUoHVg+lfw9Rg3Yl298z5eM2Yt9TUzvg3RWZ3Mh f3myd5AY72QLbEwgT4AiW3nZh/8qAFmCACVL1jRXHwOxIvYe86OC4B1eKve9gu8DL IeKf+ZSKwk6pxwUoKEcqOeTS5fL/fz7WvXv9xhOnvpusqlTUjiQzbQz/91u8sFaN/ PLD16G5+vvhpXCv7TEl+wFWvahpAXDD9ccbPr1DvIcroNvFg7jDirawh9wLilPxdM 8ejglnYzvfcRrxrsfG1lLnrf+aGFFktTlrKdCnBpW/ZT74IvoS8a3xOU1k6R5G/zD P5G3wHvSPOhGlUrfqtH6kcqVeEwZPrX2/zYud5N2yoDblaLYt39zSeK66XbgDr5pq Pa71JZvED0uZW0We281tFodcTVIuks+giny6mkYXTY3Pa/eV3yAKdNNZ+r8FDzRDL x7RyhFEAEQJmuMgzMcSDoyWhEPLidrSlFC/rVWiCpEoNTavf9V7MU9ANvdyJixmNY Gvcek06RSNOu8uYXI4T1DlH+pAaZzoFi1+TmufuIh9eWQkBkdeEHL4SUG6IEz5hKQ Uzt0f3axIihnTlXgw/T1dymnHuR3CwnLvZK6y30ed30HtXflidUXpbv3WVUed6eEI k7OJGaQfjEsts+RpJPYqzcgcfu1WW0zo2xGTeM11UwIDAQABAoICAQCvH7QleKAMe 0VmvilgQ+PAyAIEnZ2vz4vw+m0H/iD4ugr0wqpHszhpM74+ggs9c/Ja9mr+Z54NqT Jkutx1zxp3UCsoXttoMGl4YRuRED0l9XkeM2r95riDK8MwIai7xk9JAKeRGavwsVB skOCczllrkBivAS/ZoSHrsitMerndbzesyKgi6YI2Ib7tNDt6AQaJixDvjkYusQIt mEopFOC39P6TVxKTUsslrCZ7leAlTD7cP0Z10kR6m9igv7Bn+Cc7YAPI+c91DuhzY 09wUxemYhB/b37+DsV+Vq19CGQi/54LpsHnUOF0yLXPKnaE6HMqDqOfviLFVf4e2z b9wyIA+7Q1paZT2fyDseZdZzj65I79+ywaMjcMtgp3nGeGurhrdk7mgkt4w71TvX5 iRy2Dbc9MfA0Aauw9h2MZwGckR21tdrfVqAjk8AbquAZRjeJKhaocuAFPGtfThNZW 8tCLzBvGHsEQXTJqw55krF0TYhZiDvnMayOW7IeU6elFrmVJ/opnq2GH15zMUdi1L gO9gFxn2Flb+DzuJ7bxKQEXHGXRuti324o1gm+5qNUbyRPzQALtbODartaaejDDck 0lRs22Gzl2orJRKUwUwgH+8548UcusDqoN8CexAeBRv1YeFStiwfbRcqmpHY6OZZT TltXilIxb8nAbfuj6nHXIoQKCAQEA2fDHR3uSwQy8mN43mmUPLd4wuJ8Ej7pIv+jl NcOgUzqsnEX7RQ/vS6i5gmrZ0j4MJIDpz8SyftYA6KtjXvtp0pwFXxuLAC2YoecWc Kz+TABmryUy9A+008n++rafjSQ7jyHx/Ft4ZybICoU9nhgpLGs8Dhx1sci4VK31bJ pRxUWbN1MUdWVE/vQRd2Zce3zhvJY0HgId6rzOas3E80CLrmcSRSSzuIFAy26/JXb 5peMM5MEwJ3roiEiHaJmrU7uRdTCCHU4f2EJruiuIfIB2CQp0oJahyZs88Q04ROCO toD3BEhfA5hMNvVfmEy+LWYIejzGMyO7izMPTfElWLtauwKCAQEA1MRuUKTETKEmp oxkXdTd1/Ziv9m0eqytOviXAH8w4qTOa5KtE7GF4SQoizFuJX4DFO+09HUVCuWpuF MlHDL6nDgo8ns/h+rZH9gGq9dI0EJqZO3qXZd1eqcCTcD75w4RnaVT25t+B143YiH yuB2fUtZ45KMJZ8TGaShTrX9d7rEAx5cpBKUcpKdf2+jG4TbEg0d5TpkArLWQaasm 0SJacjxvwLizx9PIO1cnUtqzKzSIc7ZPPaNSF/ggnBvzsuTsjN7Yysj6g/NUNeWaR uOQHi1jKiJFOPyuTynAFb8dW/TTamM4tNriTPWRDeWxn/cCzjN82LuZYSFp2YSwAh 9iSQKCAQEA07f2v9iqPmRTKuZledXHIzYAernwQngjiFuFn+9FZtVPLhlF+ld6jjO A09Nde8LPJP8fJLne9RyMkXs9892ldfYfub8WD7w71LybriZAfDC/kFJA55wCfOR0 kbar1P9n5KqDXDa4Bu1D1ASQUW8DQKWEF+lJ5MYrXAgKrdBUs6HlIZ/R9iHFHzCXb zi/iy9MVFpOkwBXc9yrYlI3jGAd0fVLaGQ5O3pFKduSUaD88UxbEDckwpXCI/LhqJ nwOE21BvVy0Wk9wmb0JE16MAjTogDIP+FVlnb6gunmyfKkcWNjlIOB2govsAw7K8c R0SSYA7fai3+hB584tO0Y7NRFVQKCAQEAtLHmJiLoe1PZKVTN4Epp5YFgOCLB/fUs YYrBYzCh6bmhfQZiavk3BA/bRutB6iFsqJkzCvFsX4bQbrReLea1o3qme9hkCiBPK ZYjhEkfk470IKdVhQqeQP4ZHMOqq33tQ/8mirkxfTxsuLCwG+E8b/OpVDOKR1B8WM 5g0lX7Bj3YtakQstsuyj9DT9YPITGMDxniUffq9MJ11Of/3VjZi278UaYLOMgJoyv PP7RwuV6yC+NmxKgxjh2yRCIRA3sb0j9nykCJUQqUF/nY3h9hQ9+z+hKEy3L87ko8 0bsvSetQqWd+v0mtK0LaX/0Urw/umARuIPqhtjXUWWEwwXy50QKCAQEAgqG84W2Kk PWOoM6mM6hH2wr+VFHltVVZB9qYwM3UDCaMltUdLyGG1hQz/iyHanXY/pGCg7x84N /W8RxYWHfHE1kTWk8I9ogIP2LlT41G8/Pa8L3TQdytotGtQVpHPCGo46SdglIGsbN b4LHdhJ5o4KCcrIjwHmDDgLQbSIvV1KzPZtHU7gY05V9PdHqOhkRcBMWj8pzYvRgC 8CkE1Ri9jSRuMn8l9M7nTrRKxZ+1/iLDeOuFKf1HBNNh2r+QSsYVa8MYmfYyK6Ex7 aic19XMgZGZ1TEfCLW6WSykCf9+Zjt/SFmmyQPBwfJmZspm6T41T8UnwgBya+T3Lu bbmdJJzw==""" PKCS1_PUBLIC_KEY = b"""MIICCgKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUY N2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0 Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYT p76brKpU1I4kM20M//dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XHGz69Q 7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5aynQpw aVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v82LneT dsqA25Wi2Ld/c0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp8uppG F02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQv61Vo gqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7 iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72Sust9 Hnd9B7V35YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6NsRk3jN dVMCAwEAAQ==""" PKCS8_PUBLIC_KEY = b"""MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAg EAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQ GO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mU isJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M//dbvLBWjfzyw9ehu fr74aVwr+0xJfsBVr2oaQFww/XHGz69Q7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2 M733Ea8a7HxtZS563/mhhRZLU5aynQpwaVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B7 0jzoRpVK36rR+pHKlXhMGT619v82LneTdsqA25Wi2Ld/c0niuul24A6+aaj2u9SWb xA9LmVtFntvNbRaHXE1SLpLPoIp8uppGF02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRR ABECZrjIMzHEg6MloRDy4na0pRQv61VogqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNO kUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92 sSIoZ05V4MP09Xcppx7kdwsJy72Sust9Hnd9B7V35YnVF6W791lVHnenhCJOziRmk H4xLLbPkaST2Ks3IHH7tVltM6NsRk3jNdVMCAwEAAQ==""" def test_rsa_private_key_pkcs1_to_pkcs8(): pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) assert _asn1.rsa_private_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 def test_rsa_private_key_pkcs8_to_pkcs1(): pkcs1 = base64.b64decode(PKCS1_PRIVATE_KEY) pkcs8 = base64.b64decode(PKCS8_PRIVATE_KEY) assert _asn1.rsa_private_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 def test_rsa_public_key_pkcs1_to_pkcs8(): pkcs1 = base64.b64decode(PKCS1_PUBLIC_KEY) pkcs8 = base64.b64decode(PKCS8_PUBLIC_KEY) assert _asn1.rsa_public_key_pkcs1_to_pkcs8(pkcs1) == pkcs8 def test_rsa_public_key_pkcs8_to_pkcs1(): pkcs1 = base64.b64decode(PKCS1_PUBLIC_KEY) pkcs8 = base64.b64decode(PKCS8_PUBLIC_KEY) assert _asn1.rsa_public_key_pkcs8_to_pkcs1(pkcs8) == pkcs1 diff --git a/tests/test_backends.py b/tests/test_backends.py index 6e633a1..10ef390 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -1,35 +1,58 @@ """Test the default import handling.""" try: from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey except ImportError: PurePythonRSAKey = None try: - from jose.backends.cryptography_backend import CryptographyRSAKey, CryptographyECKey + from jose.backends.cryptography_backend import CryptographyECKey, CryptographyRSAKey except ImportError: CryptographyRSAKey = CryptographyECKey = None -try: - from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey -except ImportError: - PyCryptoRSAKey = None try: from jose.backends.ecdsa_backend import ECDSAECKey as PurePythonECDSAKey except ImportError: PurePythonRSAKey = None -from jose.backends import ECKey, RSAKey +try: + from jose.backends.cryptography_backend import CryptographyAESKey +except ImportError: + CryptographyAESKey = None +try: + from jose.backends.cryptography_backend import CryptographyHMACKey +except ImportError: + CryptographyHMACKey = None + +from jose.backends import ECKey, HMACKey, RSAKey +from jose.backends.native import HMACKey as NativeHMACKey + +try: + from jose.backends import AESKey +except ImportError: + AESKey = None def test_default_ec_backend(): if CryptographyECKey is not None: assert ECKey is CryptographyECKey else: assert ECKey is PurePythonECDSAKey def test_default_rsa_backend(): if CryptographyRSAKey is not None: assert RSAKey is CryptographyRSAKey - elif PyCryptoRSAKey is not None: - assert RSAKey is PyCryptoRSAKey else: assert RSAKey is PurePythonRSAKey + + +def test_default_aes_backend(): + if CryptographyAESKey is not None: + assert AESKey is CryptographyAESKey + else: + assert AESKey is None + + +def test_default_hmac_backend(): + if CryptographyHMACKey is not None: + assert HMACKey is CryptographyHMACKey + else: + assert HMACKey is NativeHMACKey diff --git a/tests/test_firebase.py b/tests/test_firebase.py index b31ac8f..1096591 100644 --- a/tests/test_firebase.py +++ b/tests/test_firebase.py @@ -1,51 +1,41 @@ - import json import pytest from jose import jwt - from jose.backends import RSAKey + try: from jose.backends.rsa_backend import RSAKey as RsaRSAKey except ImportError: RsaRSAKey = None firebase_certs = { "6f83ab6e516e718fba9ddeb6647fd5fb752a151b": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIP5V2bjX2bXUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODMxMDA0NTI2WhcNMTYwOTAzMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKHHtOMXBD+0YTtZHuzFrERiiwa+D6Ybq4SUHlicgRPV3Uk2\nvnTOqg1EhxshEXqjkAQbbRop9hhHTc+p8rBxgYGuLcZsBhGrnRqU6FnTTiWB1x5V\nvOfCkPE60W07gi8p+HyB8cqw1Tz2LnRUw/15888CrspVeumtNUkhXSRKzeS2BI4l\nkuOMkqmsMSu1yB5IZm5meMyta1uhJnP93jKmdar19RkZXOlFcT+fsSY2FPuqvDvX\nssChgZgNV5qtk0CIzexmFJaUFzpKE/RxqdIJooB1H83fUBGVK+9v3Ko+BI+GEvUc\nxIGAEWu2KrbjwPNzzC3/UV9aSfHEOJxQoutPviECAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAIHOiqxXm1IcuXE87ELyKYDG0/gZPzCHz98h/x0LExrs\nd0bOYOIA08rt6qllmP24oT3hQt86HmDb932pm/fjaLL68x81TjYq6cFO0JxOzts+\nY+9XxkdP8Qu7UJ8Dx+rRvDN1MUxLTvBVXdamhkhDusx7PB5kK1ixWtf91qrl/J9e\nUYQBnJ4E9wI8U5HVkW3IBWvsFt/+gMO1EcoNBdB2cY/4N3l3oxm5PSNDS4DTEs2f\nAYZDqo6PJt2tTRGSmvLBKSCqcT7eWBbIwBht3Uw8CvOMbVYGBWjbFeua3Q3fe+p7\n7UbFOLIvSGR516kyZqxy9pLoA9+2TvbpYwWu6mLCZtg=\n-----END CERTIFICATE-----\n", "fc2da7fa53d92e3bcba8a17e74b34da9dd585065": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIINfZYQW9uekMwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODI5MDA0NTI2WhcNMTYwOTAxMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMvfJ5DY7lV4txW0zn9ayMxwAp5BzUhyIbuZkmsmMLRrNl+i\nid4lawojB846YtcTPZLD/5QpXRumAAUI5NA023fxaUdriM25zewpSnZWs6eUf0O6\nONES8Xk4WD2fbyPz6cgnsFEfMslNd3NypRiB9fVG6LFj6TFHC64o/YEeQB2dwkJZ\nXknKSEkFJSRC83TiHUlWzaRjmTdGRrvGEWHxr+xJltP8tPPlJUKu2VadgMbGlkKU\n5dBRhvWwZZW0zJupuKzd27O2lPkxfbx9vrUbsfqZcN4OY5Xg+ijQJVTv0/qcplsd\nPZ9Uui0QsBOPbrIO+5/Tq9FIBqxzUlpWwetv6pMCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBALqWwzIQSK94hxTmxlA+RoyMvb8fyTcECM2qY+n+PDb5\nMvt8zqM6AwGjK1hvcUg08BEsnqqRqC81dkSEReS9KCoTY/oQ0sCCpwL3QP3puoxp\nfZU9CSwvnrFTJjC2Q/b8BlWta4CSDwpxpy/K3wm6tRn5ED4rPcP4FRqWU5jyHiug\nRrNkKiG7TeBBvQ3ZlF9K4JSx1yn9g7EvPBcmygop5FIKI1uS+URxeyavtlwfnTTs\nDtRVV/x0LDkHoJ2Agy7l2MqT7eoRKh5VNucQONLrcZT1AY02eZi/WVSjgpzC48eP\nV9xlcgIaRbS/JDULYgW5h0uVdRNqSVGJ6yBLXT2uaBA=\n-----END CERTIFICATE-----\n", "8226146523a1b8894ba03ad525667b9475d393f5": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIWAKW/IRYcAwwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODMwMDA0NTI2WhcNMTYwOTAyMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALJAt+ws+XNdDnDSYFp0YnQ5e8QqfMFrwp1l1r/mNSUF840I\nsbm50Z89aNpQgFOsORS/TYyHOeasiBhsJ5HWmfxo0PBTFifKI/OedLlltxZZCHa+\nEO/75Fbeydexokvfq6thT7C+xL45kJzbvKKNAw4WCAW6vwzyz+d/IrWCs9Iqa2ZX\nSiKnMPzPxZj6s+AhHPVxsR8dBMZ+NdK/wh9OcPWjLAxLEWBvd0Gp315bIVjVc9pV\neYcTapu/s4DSwgz4twovAyUziwsa+HJ+2FFNDZExf/XQUVBW5le8gGEdfl3kW1yu\nzdO6e1LwVTDAXULydPBL5lb6vTX2/ICmMzHXzIUCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAHyACbK1WfP9WspLfxvgJaNvwvygnP6cggLMvqq/dRxP\nlemvxfVaHK19sIXI6p0H4RBjI9FID5otzuyV54p1LBKgLIMTWcMYdL0wieeBg4Ud\nwgLEutIERpJU5oRMpSuZZYW75d0o+U1qOEhDswliqW1xofxNjRgNyrOYc6hMJzIS\ng9U4C4fplT/m3x5uQNjfzN/0CxfQf54WaD15w1lPGQAMJSWQDaxDTi41bW0Jwp4N\ndshOVn+btUUwL5TXDKaVkg1IHfG57FwvPJ5hKs4pbP5SIm+Sc1utIMMTBsRDRJVK\nyHaB5Bj9KcpQk7FvdT/KtzetPowhnxu9ow+KJcnP+7w=\n-----END CERTIFICATE-----\n", "dd694b16c1b0ce31878a72dfa6c0cd4db3dd7edf": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIffru9igojE4wDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nOTAxMDA0NTI2WhcNMTYwOTA0MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALaxpG4i7EgYpzaJsykaZzKmTTnm+kIPJBKb6t41ByUWt7J+\nnoUmlMiAVkXj7GAmc3usroJdYNZ8iMSpAWsIMgg7HLrqv/hMDY6+33rCqsvXD2tF\nCtJbRKzSMKu+AIc1uirkX3L3aHfKRzFbsr+8JqOigY3sVAb42FeATVHB0uCRyoE5\nfqxbt8nIPCFR/lFP51L0Wf5hGIH5kHJEuXx/7GOUQPN196P3sRI9jLv6nrWqGTAR\nVhuY9KXRz0jlVQeKZV5mWstcIXgxn2MfzfoHx4nuSNknJdrfHNp0r2XPf9Fre7Jd\n73slrVUwL2VWyZJdIBxJuYz2QjEQLzz+eJGyWcMCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAFTpRr9/cEkFHSbP5c7gr926kSxe1e9u9JjzR7l9Zv5l\nfskkLxIZcGlx/FoccWCwDPYl2Nh0Pr++TJ2hWVe/LpjppUxl4QVqfbVzyJezn2UR\nhLnGASQ0ckPoNTJhxjA6PVGtyXWB67oCDEgz/Pl6jjKEMtilyXh93rBmOpt6jq9e\nlwiZaa5wTUwIhHI972rLveYkssVkspmp4RIWHoh1nxUjYPMtcTCf9GFjEMLNdDBj\nYldCEzL34V60ObBSkzV3Zx7UNwoa80+SEJc9gQsBHVJbjXl7V9ODL52OHnciiEA8\n+d/xy2tBzdCD5EUR3aaYZYqQ16VV6LeU8FoxFn6/nxw=\n-----END CERTIFICATE-----\n", - "f4b0a5c73ad85a5da09f0e7f76463631339e0bbf": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIWDhBeVUilCcwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nNzAxMDA0NTI2WhcNMTYwNzA0MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALRWaRmoi5EFyj5TBrUGKFI6uBJ4x9wSHq9tlRL1qmnwzdNb\nlDoeoh6Gw3H54IqM0XqjZZwgV5KXOQDOaoUpMBRH93x7Ma7NjhiDtpQr0JSbFIQL\nsIay/VxQ9gfa/I83HViEAbF1FXjhBKniwFKUv26mU30upZfsDQkHM8OLc/iXRvhA\nYn7S732Oefdv0kJ9t3h+WOGKGVkYfDaAGn5Uyzx+9oyyLY33borKOBBzphSQlZCr\nL569zTXvvLgvdStrsPGaiRGj64DGXD6LCg6acLJcMUvlVUO6THHJHVgp8pzlrPQG\n3B1rZk61lZqJyjK/nTi2tY9GPLfdxOfDAMjNoz8CAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAIlFwO3C+X2+na0nLjR+zQYGHzZYqFe4V67P6ugFJxun\nxP8pyDCYAGer1mkDcIyDacdQ3natNp0xv61a0yk5tSmDYZbXZRTFdLkf/GzH+VmH\nEMl5W4TvxjAe/x2opm3QUaPC+jVlvndcP99FF5ULFp7/PwSTp8uzyrd/fhSFaxhq\nuIW4syNzDSpDItzUsiKCtsKGYX/qvd/cNP8cXlPd5rWTM4Sic9Baf2nXuHaZRkBr\nSJYcxdh8xbGsY1tC8TIgWot6GXtldNvXDLqRUwb2t6Rr3Tqhbc0CcHndTCuHXf0i\n0s9jU/UCrNhhmaD0rZLHQ2tuN6W/xpOHKtO0a8Lys7c=\n-----END CERTIFICATE-----\n" + "f4b0a5c73ad85a5da09f0e7f76463631339e0bbf": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIWDhBeVUilCcwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nNzAxMDA0NTI2WhcNMTYwNzA0MDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBALRWaRmoi5EFyj5TBrUGKFI6uBJ4x9wSHq9tlRL1qmnwzdNb\nlDoeoh6Gw3H54IqM0XqjZZwgV5KXOQDOaoUpMBRH93x7Ma7NjhiDtpQr0JSbFIQL\nsIay/VxQ9gfa/I83HViEAbF1FXjhBKniwFKUv26mU30upZfsDQkHM8OLc/iXRvhA\nYn7S732Oefdv0kJ9t3h+WOGKGVkYfDaAGn5Uyzx+9oyyLY33borKOBBzphSQlZCr\nL569zTXvvLgvdStrsPGaiRGj64DGXD6LCg6acLJcMUvlVUO6THHJHVgp8pzlrPQG\n3B1rZk61lZqJyjK/nTi2tY9GPLfdxOfDAMjNoz8CAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAIlFwO3C+X2+na0nLjR+zQYGHzZYqFe4V67P6ugFJxun\nxP8pyDCYAGer1mkDcIyDacdQ3natNp0xv61a0yk5tSmDYZbXZRTFdLkf/GzH+VmH\nEMl5W4TvxjAe/x2opm3QUaPC+jVlvndcP99FF5ULFp7/PwSTp8uzyrd/fhSFaxhq\nuIW4syNzDSpDItzUsiKCtsKGYX/qvd/cNP8cXlPd5rWTM4Sic9Baf2nXuHaZRkBr\nSJYcxdh8xbGsY1tC8TIgWot6GXtldNvXDLqRUwb2t6Rr3Tqhbc0CcHndTCuHXf0i\n0s9jU/UCrNhhmaD0rZLHQ2tuN6W/xpOHKtO0a8Lys7c=\n-----END CERTIFICATE-----\n", } firebase_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0YjBhNWM3M2FkODVhNWRhMDlmMGU3Zjc2NDYzNjMxMzM5ZTBiYmYifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vd2Vkb3RyYW5zZmVyLTIwMTYiLCJhdWQiOiJ3ZWRvdHJhbnNmZXItMjAxNiIsImF1dGhfdGltZSI6MTQ2NzM0NjI3MCwidXNlcl9pZCI6IjRjemVXVllIekNNVnN0WEZOYldHVXBKYmJTZzEiLCJzdWIiOiI0Y3plV1ZZSHpDTVZzdFhGTmJXR1VwSmJiU2cxIiwiaWF0IjoxNDY3MzQ2MjcwLCJleHAiOjE0NjczNDk4NzAsImVtYWlsIjoic2V1bkBjbXUuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7InBhc3N3b3JkIjpbInNldW5AY211LmNvbSJdLCJlbWFpbCI6WyJzZXVuQGNtdS5jb20iXX19fQ.U-fYjx8rMm5tYV24r0uEcNQtIe3UKULxsHecLdGzTbi1v-VKzKDk_QPL26SPDoU8JUMY3nJQ1hOE9AapBrQck8NVUZSKFMD49XdtsyoN2kKdinpFR1hSxIE0L2dRStS7OZ8sGiX866lNa52Cr6TXSsnMD6N2P0OtVE5EeD1Nf-AiJ-gsaLrP4tBnmj1MNYhEYVHb6sAUrT3nEI9gWmeKcPWPfn76FGTdGWZ2mjdaeAG4RbuFL4cHdOISA_0HVLGJxuNyEHAHybDX8mVdNW_F4yzL3H-SmPFY5Kv3tCdBzpzhUKfNOnFFmf2ggFOJnDsqMp-TZaIPk6ce_ltqhQ0dnQ" @pytest.mark.skipif(RSAKey is RsaRSAKey, reason="python-rsa backend does not support certificates") class TestFirebase: - def test_individual_cert(self): jwt.decode( firebase_token, firebase_certs["f4b0a5c73ad85a5da09f0e7f76463631339e0bbf"], - algorithms='RS256', - options={'verify_exp': False, 'verify_aud': False} + algorithms="RS256", + options={"verify_exp": False, "verify_aud": False}, ) def test_certs_dict(self): jwt.decode( - firebase_token, - firebase_certs, - algorithms='RS256', - options={'verify_exp': False, 'verify_aud': False} + firebase_token, firebase_certs, algorithms="RS256", options={"verify_exp": False, "verify_aud": False} ) def test_certs_string(self): certs = json.dumps(firebase_certs) - jwt.decode( - firebase_token, - certs, - algorithms='RS256', - options={'verify_exp': False, 'verify_aud': False} - ) + jwt.decode(firebase_token, certs, algorithms="RS256", options={"verify_exp": False, "verify_aud": False}) diff --git a/tests/test_jwe.py b/tests/test_jwe.py new file mode 100644 index 0000000..ab005bb --- /dev/null +++ b/tests/test_jwe.py @@ -0,0 +1,528 @@ +import json + +import pytest + +import jose.backends +from jose import jwe +from jose.constants import ALGORITHMS, ZIPS +from jose.exceptions import JWEParseError +from jose.jwk import AESKey, RSAKey +from jose.utils import base64url_decode + +backends = [] +try: + import jose.backends.cryptography_backend # noqa E402 + + backends.append(jose.backends.cryptography_backend) +except ImportError: + pass + +import jose.backends.native # noqa E402 + +try: + from jose.backends.rsa_backend import RSAKey as RSABackendRSAKey +except ImportError: + RSABackendRSAKey = None + +backends.append(jose.backends.native) + +PRIVATE_KEY_PEM = """-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA3AyQGW/Q8AKJH2Mfjv1c67iYcwIn+Z2tpqHDQQV9CfSx9CMs ++Zg2buopXJ7AWd03ZR08g9O2bmlJPIQV1He3vfzZH9+6aJAQLJ+VzpME2sXl5Boa +yla1JjyoH7ix/i02QHDTVClDMb6dy0rMVpc7cBxwgX54fcR5x3AMscYCTQrhQc7q +YRzoLTfP9lGJT1DgyGcOt4paa77z4uqqaQxQ4QqxM9in3DU0mzVxXigHVakjiS6v +kSNEhSl+VLIp1sHiOhOSpcxWkhTikjm+XpwE5H0L9I1mQ2e2nTvX7uADg/pgFMy0 +uP833rQzTxNqTTPJZFLtLkTyq1Hr2MUeQ3dRNQIDAQABAoIBAFK9+pVGAVeubGc7 ++4rl5EHSqKheQC/RRZGps+TILotG0n9NlsTHong0XpcwLn3b+89unemn+yorNtml +hRveZF3xLKealdppiVtuKoOBrsqgrWAHHNnGntkg58r9xRghYgv7IMu9tEGJPoZJ +uuo4daYjW36l0qLf9Ta0AGH8ZbMX2LnNO+r4EQmZ1YJShEYOS94WJnFB7XuZ/bQH +AI3IRPkQvXQNq1nnMxhAj91hOhJvTVCS04yVVzMkntcpeNP7pc7ARtSA5IepJvdK +HbcoSQ1aIK/NPkhiDs/KOoWdnB8Mqr3fXFTVJ3/YTJKwODugJ5QCbSyIC8JewgIn +d6mA6iECgYEA7028RNk65c5NRkv6rkveTT1ybrvYUUO/pbAlS4MqZmtx69n4LFrW +qicXw7sJd+O8emyvF3xHPAfVviJKg6yudtI0nM9WUuOgKr+qoKRWJMpspXdpjTXs +AQXrFAJjrDIFujsbnRmT2nbRX8nSBWvI5oSG4JqILWYs0OdchIkPo0kCgYEA62bq +mjnlz7Mqvznf8b9jOSEJKub81aUz/fK62gXcEdvffUdlDecAzotjryI678TvEBpI +w1rmHLND60o+Lczd3quyEPQfYrf8P4/6sqGfE/QtB7zKR1bXmkV0dNlr9h6zpm/Y +BpLNiqr3Ntf4OCkKiD6ch+sZ4NjKBCwzodolUo0CgYEAk/PEzfBcqM5nGmpJX8/K +bojqIiqDcKLpb4A7XreG1HHjqkVGWe4DwImQ+NO/497qnepqSqPsyuGxNe+vkD+I +UjBelQDfxzmywhtkXBOeqvp4N8lfeg33jx5gnCtqAoGe5ug6h2PT9QL3Kjj2X6Gn +QVZ4qY8BWMhONw6ENfEjuPkCgYBP0ps05vMdpgSVyXs9z4dG5QPlz2Pm0lk6AKgJ +rDj+uU8kfSQwPafRYgTQa0wO5/mkvTT1QYqMKuGaFJfXEgQeMJx2EUHfSMI5j4oU +LqfxrTfjysnQvQrpHioqQVvRnoGOq5hWSkt2fRjNORjLemc+4fRURo2E6B5Aofh0 +JrPHNQKBgBGYzDGJyFnu7GYTby18aPNkQYweNDM6aZ/tUN8yZ4ryq7QnodiKLe2b +VxSr8Y+1w4xRjN67PGrS3IpQX9CAoTqyBN7VLhuq/mixOPccmo/5ui3fig/WEYwK ++ox4tfIuhfmskPNS235vLwbNIBkzP3PWVM5Chq1pEnHQUeiZq3U+ +-----END RSA PRIVATE KEY----- +""" + +PUBLIC_KEY_PEM = """-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3AyQGW/Q8AKJH2Mfjv1c +67iYcwIn+Z2tpqHDQQV9CfSx9CMs+Zg2buopXJ7AWd03ZR08g9O2bmlJPIQV1He3 +vfzZH9+6aJAQLJ+VzpME2sXl5Boayla1JjyoH7ix/i02QHDTVClDMb6dy0rMVpc7 +cBxwgX54fcR5x3AMscYCTQrhQc7qYRzoLTfP9lGJT1DgyGcOt4paa77z4uqqaQxQ +4QqxM9in3DU0mzVxXigHVakjiS6vkSNEhSl+VLIp1sHiOhOSpcxWkhTikjm+XpwE +5H0L9I1mQ2e2nTvX7uADg/pgFMy0uP833rQzTxNqTTPJZFLtLkTyq1Hr2MUeQ3dR +NQIDAQAB +-----END PUBLIC KEY----- +""" + +OCT_128_BIT_KEY = b"\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xce" +OCT_192_BIT_KEY = b"\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xcek|\xd4-ok\t\xdb" +OCT_256_BIT_KEY = b"\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xcek|\xd4-ok\t\xdb\xc8\xb1\x00\xf0\x8f\x9c,\xcf" +OCT_384_BIT_KEY = b"\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xcek|\xd4-ok\t\xdb\xc8\xb1\x00\xf0\x8f\x9c,\xcf\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xce" +OCT_512_BIT_KEY = b"\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xcek|\xd4-ok\t\xdb\xc8\xb1\x00\xf0\x8f\x9c,\xcf\x04\xd3\x1f\xc5T\x9d\xfc\xfe\x0bd\x9d\xfa?\xaaj\xcek|\xd4-ok\t\xdb\xc8\xb1\x00\xf0\x8f\x9c,\xcf" + + +class TestGetUnverifiedHeader: + def test_valid_header_and_auth_tag(self): + expected_header = {"alg": "RSA1_5", "enc": "A128CBC-HS256"} + jwe_str = ( + "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7" + "Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgN" + "Z__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRir" + "b6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8" + "OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0m" + "cKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" + "." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "9hH0vgRfYgPnAHOd8stkvw" + ) + actual_header = jwe.get_unverified_header(jwe_str) + assert expected_header == actual_header + + def test_invalid_jwe_string_raises_jwe_parse_error(self): + with pytest.raises(JWEParseError): + jwe.get_unverified_header("invalid jwe string") + + def test_non_json_header_section_raises_jwe_parse_error(self): + jwe_str = ( + "not json." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7" + "Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgN" + "Z__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRir" + "b6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8" + "OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0m" + "cKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" + "." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "9hH0vgRfYgPnAHOd8stkvw" + ) + + with pytest.raises(JWEParseError): + jwe.get_unverified_header(jwe_str) + + def test_wrong_auth_tag_is_ignored(self): + expected_header = {"alg": "RSA1_5", "enc": "A128CBC-HS256"} + jwe_str = ( + "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7" + "Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgN" + "Z__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRir" + "b6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8" + "OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0m" + "cKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" + "." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "invalid" + ) + actual_header = jwe.get_unverified_header(jwe_str) + assert expected_header == actual_header + + +@pytest.mark.skipif(AESKey is None, reason="Test requires AES Backend") +@pytest.mark.skipif(RSAKey is RSABackendRSAKey, reason="RSA Backend does not support all modes") +class TestDecrypt: + + JWE_RSA_PACKAGES = ( + pytest.param( + b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.qHxZy-MfqRjCDAieY5AoU75XRGS7S-Xx4NytHgNa5dmGh9R8q1riHyPw5Hec_D395fKqV75u1hKke5r-jgiDTaCicQjOuxM2cSaiFlUid7dk5zIucaKH84N8jMzq3PwBePmGftePM2NMCzs6RvWBFP5SnDHh95NU2Xd-rIUICA7zIBXTwNRsB2LM9c_TZv1qh59DYoiSHWy94WXJBNFqViuVLmjVz5250J6Q4uRiYKGJKEGkfLDUp18N97aw5RQ35jJF6QyO5JkeLFTA0L10QAEtM8RjBRrKYgJ6fJLCVbHHTf7EKdn6Z-4cIZKtYe2d7PPKa0ZWZvtYTuU1S6DgmA.gdSr6lSIci4GjzMsdLaK6g.4ynh6gGG4dzxpmNfZHo6o8Eqp1eXRhKzI2Tmde-IulU.cFUhLtodRUqZ1GfSO6e3pw", + id="alg: RSA1_5, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0.Ju8YCub_jjFt4WR_pOIyeiXLtfwhUl-FMNETu3PMRVV8v6pD2-X4AFNWeA2pAX1_DkUIJEP8J3mjFdZB_ah6wb1ab0je-aSk3d8di8ES93gv_DkwWHkz_cjbm2At3JEh2gO252O3Ychjn8C0gMnLiXJN9Qmg_nF1drpvSdhgFz0FEI-2NlhD-0d8yy0ROMaMEby7aX7ouXP6QI3PKiwFYgPB-dtMzvF2cmZl_g3sLde9l1-U2e8JIpAW8vqQCO8Jswr0B6nH_LjUIBUEWS5vipqTa_v9siaAgLI46T5kEMJhnRVjJHvIkfnFABn5fCCVtgx2VpVrNkcejqvfLjIyNg.qyfq0GH9NgQOjuyEIKRQdA.FUb4QogxGaOslBqaTlcYqGGmhMXS8uTXNY0mpV7VPkQ.gi1jZcKEJoBey_5YBxSFVDnZulAlRPkq", + id="alg: RSA1_5, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.2r5K6UQ4a8PDar1lpsLBNnMSwPuffn3vVnI-fbFCBKTzRUSgzWiMYKd9PCBFQIA5D3E8bwQiMY0tgiHNuCZF4PaLJp99SVKkbwp0H5681mFgpQ5c-QtPHMa5fA7_zOt1DRN67XddKTSKLm7_3RQ2twU4rg3DVS-aElZZSV74Rip_KKeoDvaoJBfPY4HPFqiR96dHLdLCoSzks1XzmRxo36cY2wb-4ztWUd2J5-_7ps1khUvffOMFJuox2zk9FYIqHXZQr9eL3n4cdF-M-tFvfjBenUThW97byckr1gyWzHCUOcaVHAP3jp1xubPahtkCpsOGAvqwiO9ahRtY0afhyw.xTKBz19OoA1Av0OfNVPgOg.FCNLcCHaOGBjQSLw8vJ_2K5ROdsm0m8YkKdkSGGzX98.M5fPe-ZDlF9xjS6YELgFS30sllUK_5FZ0vBqmmKCWpY", + id="alg: RSA1_5, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4R0NNIn0.QYbUBjDR7tf1NsbLOsVg3oub--eOgcm-a9BWJ3VIlwUWlE6ybdNFY-tgib69bFeDVJUgFipGbjpx99xbsn12F4dIZvDy0S9XWqKZ4GHXCtcButxyxyusQl-Qw0Myfd9OFEDmCnjCcU_Z2UamlsSK5c9OQa9F832bwlsOvufvexAUIoqNI94J6MCzWYn03zNcuKXd2EzbTXWRcxUL5RMQ_fFJb5mVEoRArw5H0Q9vCsjUkBGfvrLNr810yZrOIZLKrUW5Gq7vK2RR8GrPX1R1NIIrWe7FJgp1qr18-74q2vkNA8oGQitH1s0UJXXYObrJYZUZMGDh5NkGHyct1MwAqg.6GmP0pU4BfLq9vft.Lr_B5NID1Jsz1E-N9Hxz4PM7XV99sg.vNGa4jT1-N3eb7MZoj7REA", + id="alg: RSA1_5, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBMV81In0.pGI9inTliv1C52i9XOAVEXTcNR_KpOrK-flxdabnRFCCqVJDmvpoE1dO84FBTC0e0lSkfuGOdXOqOhgNho-rwtpKGeuAkk1X8NPmi-Cre6_hyZRcn-0M7tn4oqN-4JIh4FXSiMEJQfu2w7wTtZLX7FQvNRWYwl0klx_VB29rCEECTxvBDORmgT5N8WaEvqHb75X1SmO-t3JAlej2lJGKlrgThH7c5SUx0g702ccaMqORJ46JXKGGABqAUSwWpXozj5MimKg1UgVT6pXdj7MQtcMv_mhL7HIbUUZdTjbnkKmU-AH8rwJdIXsR5vosnzv_xOxf4BSOutkjqCBD7-psFw.AMBAA8ZpTm0c96TS.ehGiMXxn8bcH0yPmi9_d47UKc1C9hA.FyF6Wl57itn_W5hphdkXDA", + id="alg: RSA1_5, enc: A192GCM", + ), + pytest.param( + b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2R0NNIn0.FmKpIISKPpeA45DVJFzuHuZzuDBc9OblwI1pa80rwlKVB7GhhTpd4aXYWRLU4qMNUfGj_Imlxc0rYdOfPa1IvCrrED9KjR5H604ruZgJZigoYCkS3WnAUnMCIOaDSP_Ye2UC4OTwnDSXRIdgnoyM-g9l3fOjgSeoc2aCSRE5DGHrgEpvzaFWDl4YDD_im7IsFEM8H7H2TAlN7ftkbKN6jd9MMRDXd6y7HYvNm4Hi_gPDM70TWhj-LIb6NmJE19EAboy8Ul8HAFdaCAFxwlLa6tFQyOuw-PLnZQ_soLGZXUeFNuYOafIjmPL2tgJiHfj1K_IPZwmWZS2d4I45He3CRA.xAUHSwvfz51m45eo.XeSm9hkA2mUNPk9eiaZx-I7mY4ZJqg.T0S3B4H4KusBzyZos81EIQ", + id="alg: RSA1_5, enc: A256GCM", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhDQkMtSFMyNTYifQ.wQh8pyyAMCQRMAeMMIXStaoBCytZ4Upd7hFqpGxkoHq6aCDjjXywERJqgx68co_vz29JkTlK0Z2UsUOLjM4M6TeEiKgw0zT7ENXehP6VeE0bo2_cCx0k8A_af2eJXpsaqIvRsdkqYCsSW96H_eq3PoqOx96DNWTHxY5OTDjthr8B5WCYx3qA1oepT1HXSfCDB_01Qg-OREMu6l4Qc3i-ci6kQfhoAHb-sowpM8tUPvOx28z9-3a5_HxWMh0jFez86d9RHCecJx1UxHMJ6GSCzd2ra2xKi1gqaiC8MZupjvVJeGEpb4uriFmw5zJ9YGnefLj9NPMvj79XTrjD4AalaA.o9RgfKTIB5wbkrRr-wkO0Q.7ejS9gM307dU3to_V3AtqukA14IhuFyLrRG9RmRH2cw.hXUMRYby8afLVMI3H-WHYw", + id="alg: RSA-OAEP, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExOTJDQkMtSFMzODQifQ.u3QeBm1xbLlQSoDZJ5QFLT5KnTBvxHuh5WCb4Yt-jRVipJ_7DWBORoAsFXV-SB3oIeRlchcPX0QK2bz_uxFxNZGF9aLgROZXmyFGUs-S_6mewqnxiCgWcgM1fOvast6d65_Zrp8kgz8oev4EiuXwb2X1OO31BEOn3aZR7QGdD6O59q6pF79OU328hpKatqBjW4IdIgg68rtA2-87Xj9VqpqUBkgzJCf-z038yQR41GNVTRzMk6N2M3MgRYUFkqUHy59TRwplWQuRZ9vmkdotRGYI0ZQ7V5PzXhqYSJnx5Y9jYlIqv7sdz_b6lyqxkrtJGBRNfAFiil4HABIobx5YDw.2oKvl74hWoa3zpABph4L9Q.04KyNsCkVQAX-s547eYJOfj6SBR3cZypu2qy7ua4DUg.AKJwqOIH7wK3_7n_DmvZ96yq1vm3d6Mh", + id="alg: RSA-OAEP, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIifQ.Kbd5rSN1afyre2DbkXOmGKkCNZ09TfAwNpDn1Ic7_HJNS42VDx584ReiEzpyIoWek8l87h1oZL0OC0f1ceEuuTR-_rZzKNqq6t44EvXvRusSHg_mTm8qYwyJIkJsD_Zgh0HUza20X6Ypu4ZheTzw70krFYhFnBKNXzhdrf4Bbz8e7IEeR7Po2VqOzx6JPNFsJ1tRSb9r4w60-1qq0MSdl2VItvHVY4fg-bts2k2sJ_Ub8VtRLY1MzPc1rFcI10x_AD52ntW-8T_BvY8R7Ci0cLfEycGlOM-pJOtJVY4bQisx-PvLgPoKlfTMX251m_np9ImSov9edy57-jy427l28g.w5rYu_XKzUCwTScFQ3fGOA.6zntLreCPN2Eo6aLmuqYrkyF2hOBXzNlArOOJ0iZ9TA.xiF5HLIBmIE8FCog-CZwXpIUjP6XgpncwXjw--dM57I", + id="alg: RSA-OAEP, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.SUDoqix7_PhGaNeCxYEgmvZt-Bhj-EoPfnTbJpxgvdUSVk6cn2XjAJxiVHTaeM8_DPmxxeKqt-JEVljc7lUmHQpAW1Cule7ySw498OgG6q4ddpBZEPXqAHpqlfATrhGpEq0WPRZJwvbyKUd08rND1r4SePZg8sag6cvbiPbMHIzQSjGPkDwWt1P5ue7n1ySmxqGenjPlzl4g_n5wwPGG5e3RGmoiVQh2Stybp9j2fiLNzHKcO5_9BJxMR4DEB0DE3NGhszXFQneP009j4wxm5kKzuja0ks9tEdNAJ3NLWnQhU-w0_xeePj8SGxJXuGIQT0ox9yQlD-HnmlEqMWYplg.5XuF3e3g7ck1RRy8.VSph3xlmrPI3z6jcLdh862GaDq6_-g.3WcUUUcy1NZ-aFYU8u9KHA", + id="alg: RSA-OAEP, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAifQ.Kw5PHADCCpBw63G-QwHuMK75gXlZzC_RJY1SH-7ABWvmnb1KWaDCtYWbNMl-4E-dlez-LKxCbATyCFo_1WKyJcRekue7YwmfSw-eYVNOYKi2al_7-xxY8vcfxnVnyIlCetGHOJPVgeDDXr1vjbdLgg2cJhO1lRi6mDypSHqKJtyhbAR3_AYdjELPMPIMQcMdsMHa9YF5vSqoj6DnB_Bc6oLFS2fSJPki5-Gq-raWUlfnGOXEMVTm3wZGyw13extRu-H8_b6YmarvQU2oSewhWwrF3fQMzCaTUNU_yxqA6x_oZrhEeTb_BL9Q6R1oYGEXBTVQhgzWMaVRD-HtkibFjQ.Vj-fCJQPordV5AMu.RQF0cTahIAY2a-1Nr68-XyghJn9piA.8KOygvGfOdn5Wr-u-EP9bQ", + id="alg: RSA-OAEP, enc: A192GCM", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.kINITl6EJC8SY4Y8jejN1lnuwUeENgXUmYMS_wb2rcMga63pDieYdbm-ENlsFnFIC8ANukR_lx5TIhULJAVtPHFqN2Yyb8sOuG6JKX76E6DuBj1RdS6ejpVMBNNsiNYXYxvjsVnHMyBCE48zur9sZGFaHa3Sw-_Nnesm0ygo96AuTTnz6L-mzdpPK-EhWsA1fGaR0g0EpGyEjMh6NGp6n4BRqIbeSSOOwVW39akcnSs5Wl3gZq0tN0kArq_0dN4i-Yuqm30F65MQrTn7-nnjQCoXGkzlPlU9Ex-jWtkbqqjrHqJy-Gp_AVY24PRL7a_N5AHr1WHrcrkLdZEHmjGRMA.g0_LDNNkHJ7hUjGe.WwVpEFWAZ0GXhk2YhysMS9UMBs-yfQ.fTSHPmG68YG7VHIy0-r8vQ", + id="alg: RSA-OAEP, enc: A256GCM", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.K6cguIsijzwwak3cqBzKlTb3izuWdFDrvClKDscxuPCfSy_dEH-WMalroPtf8sLdEa1ocrZF7udDQk6_uhD3BGy4pytFvkIy8H9jw2o7bYGU7M2qvm7CKrAE2rxk-CU4CRZItF9PWIdKxKSdvMd2lojVgLuiQKPu0EvZFW4OeV4X77Fy-0b9PcGkbkJ9iehKHk9yjqGJAGMiyTOse7_-cyXgLMJgiSKQWPfAgHYGPN39PbH_cPjxGsl4WwawmUxnEmcQ2ctVrtfvbieupGpL9LkHXIf3I08LXh8hbYGKksWeZOBDhmtKWoAnP7PrjRNeAHIag4NqTlnA8ZXx7dtS2g.uU6nyQdGTAvfbNijkodnfQ.02Bukf1CnQWB_jYUDFSooXGzqDXW0QyKvIzE-slzQtw.Tu7u7yN8HPlS7oHmmc-OQQ", + id="alg: RSA-OAEP-256, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0.AKPATE5ww9Lcpbmo7OSA3ulVO4Y17mni5sYyLoc4Lvj6Wn9bHuzhFLyPA16qDDJsNE5pXxC5wemuAQugXQReeU_nSPsFYE_D7tUR4jMCrFZHMUshq0Cml7bgc34vXtBuxSMAHu16JjFI52mZKTHjFcBqCxDHE8EKWf7EdaPZf06swWKeZAnOAaRh2i9wVMzmpCJ9cFCYv0T31FTkr2XG1ydgZP2TAnMevRuTvtZ6e5xsc6lq0IH4nQCqKp6Hnb8aaoiKKbQMHNWAcmJzWYBpM2Sesv6zvzkacASMjwvx301dQKFVWV5x8Ocx2klcPFNdIgevWyT0-mLbbxgVAWFiaw.aoWEVUUMXkE7jbBBlG6UTg.fQmbAROAo1D6DHczAX3MH_eJfvRVHveJt6po1_jRud0.JSuCoAEXq4JUbZYYlGSqXd70QSr8V0U3", + id="alg: RSA-OAEP-256, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.nYIo9bUQgrQlnANR1IQI7EKPU75R2AoJR_44xXr5fEjEf444ucNbQvarO6HN5R_LQMEb0If7b8VyViMku1LuuFhYAoIfToT6SCcUgWG4vhN8mdc2Y4YsGqyF4k1c_EbQ3Gka_O04VZyhqukwpKUr89ASzqyJCWoP3kdiVfdjIkFnA_ApKGhnn2AwCy9_y8gW5TIVddYcOrQNVJtmxUWTgw6AxJSJkQztNfny6rbWdygXdeBXq7T4uAZYDquniE_h8f46SEUBb9UuMCq4eKVJZYJfPrKBVBMY9vncm-HAhl_IHzegLSJMgBWq_-idGMooxAypDg_Zi51zCpxinyrKeg.BiZjLouM-sJOpTprqKNVWw.0zL9BEdBAglQ-DQ2pBjJrRFsUt7qugRp3_nOY-sr75c.mcUVI1GvddAtqDMzElYzshrtS1GgnrUCb5brd2qzBlM", + id="alg: RSA-OAEP-256, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4R0NNIn0.I8HnspRs9CiFyDyumZm5YthOVLl8Vn1unThm_EQd5YGcn0WPqXtrKeAWoP4rfOn7XaRNYeuLowpHEl-CzCjoEPEW-vui-t-P1JbDH6_wGwbdVIppdcwS6Npyv5qCNI21gPBDUB2twytEGqaYGKbbexxS8iE9iU4C_Wp-42axvUKEpxxNlQn-gPmHt4ZuzMGbI9Rl5wzT583SgmHwqXTklVC02aWQY2xQYelq5IVK-UBQ8J_NOBy7SeNeuAtmh7YxLGucSVlTqmzHImkOxsDU2UEiGJK-u8eGrgawx7DFSTUx8KXeMpsF2qe87PZhkSthpaqLFj1ZFQmVycnsN28IFg.C2qD0Dpiu2xWiDKj.o5WfgRbXOMzosaKtFCKpRyZ3nHJqLA.l8iOYFrtzGgd_x8ToB5d7w", + id="alg: RSA-OAEP-256, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.f6ynrZWg8-lerNxAa5_i7S1KUmxMD7-d_DvpBEuSgH6GmOu8jkAiZDNdiX8EcsXGrRiZKNa0So92uRLRZNQ-gb9DAs8HCiXxERYkxN4EMjWlCq8T5gLQunIC-DIotde8deZFNnechKXmrO48VTPbfb8DyAwtXPtWJUBptldghCLXP63kwLpcQKKMNcAw_E1rAT6mJAiTnk3bOfKOZqdCIpwFfCPoPE-Ign_nmh2TlDX8VFkC2ZaT-CEwiQYhjmDrm6a9S3OEIfeKF-rkiGxPnrQCN3lZN2kCM5V2Wa98zmEYd1Ce-RuxB9GKAd4RUpoF84UtBUN9sGdNSasaTLzhHg.yQfUDlEQ88R6NCTm.sverha5tzKHC1T02_9WnJnt1pCmxDg.dxi-5Nz1-9u8becvm-z0EA", + id="alg: RSA-OAEP-256, enc: A192GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.G0e3bfJXhYxHn-rnj0FgMd4obOFIg2DTNDKrgDLN_q7KCDYd4jmABFRNIDg_hjdHEMn8xxPhzXVFyNEGvuMPQtc9Eg6WUFdbMwwuEP3VEmTXz6qbE0E-yVC6SfUQxwbyf7jnx_bDuyKd67LaOLd7K6CyiBm7NYlFHNvGAXVEEsizCBSuGbGhoHLVOuQ0IFaW8qLyaMqLfoiwZpTajwTC_t3kyAK-WyD7lhPbUoNQd8Xuj5xEoAXxCqi_LVPgVRGaM9vV-EXERJfTrLt9D6NNbh6DpqDy4jvJpwqXGu58SQUe53gRxviPNvAhm6dWz8xiQ0VlI6fgu8QUc8hRi-f1aQ.A7LLQLgEoU32zDF-.5KvzCLZD6buklVSzHiJf0IlL6zU_Zg.3hs8tmElT4SpfCRhcAtHNA", + id="alg: RSA-OAEP-256, enc: A256GCM", + ), + ) + + JWE_128_BIT_OCT_PACKAGES = ( + pytest.param( + b"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTEyOEtXIn0.n24LSLkqXdWX4YIaOj9dwlF-1t7hTytKdO5hqg3dQ24S6kIATishhA.JpEb2cELXXsKg8A2mIiZcQ.lbEuxBQPOy0osKUSjq_evT4GWB8U9EajBoe4HVLYb-U.9MTdcq_2zePAwKWdt2ORxQ", + id="alg: A128KW, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTEyOEtXIn0.RxCzQYdCBk5KR89bLFaxXnMI02b2XjHll_fIALg92FDdvmBj84kMKRs3CYszqcLsEC5pZGji_cs.qItxFbHqLvUOU9-_kOldpQ.GEY1cC2jX2AZH5fBSr9JAuTNjL75oXLg_y_f5k5qrpI.dbx5ZSyhCdsR99uz3jlzdRBqq_bWr21V", + id="alg: A128KW, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTEyOEtXIn0.ByJ5W2G8_vjD4Rl5kr6mYqiADET39cXvhhKQqTcu0OFFlBg8b5Auz1-n8LmPB-NF_4CTxd95RSn6Ykm5-CwYuRZ6plIh_VV_.YN9zjSsy0Hyq3yFR3RlKCw.9m3n0fZDmxxamWKoAvoyjCJtKJfLlc9U86tk5YgPz6Q.Mw30riFfQ7DbCe1pylfdN7XBhOnU58IG2g5i9-Stj7I", + id="alg: A128KW, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.PYaHt_cEHDwdKnmYyjkCg7T1HKrAy97a.WuIrAs7jHSsXqf9A.7g_Qp6DlNVrPptVpmzFDJ_1VPljD5w.RJXqRwBMyik9V1p96r-zFQ", + id="alg: A128KW, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTEyOEtXIn0.4lzeEoXilgqxK5mQ4_hLBLEygUe8bVhVTKjZ9pKPezw.Xf0HU0KkMCXEjeau.vnK_Ec_lnrxENj0tE-eLyPX3UO3vrg.vjIWpB_TtA73v93E0I7JvQ", + id="alg: A128KW, enc: A192GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTEyOEtXIn0.3PhAqtq7SE0CHpuhVhEViywAOn-w55vBvN1iGWKCn3-nioam-h-GIQ.4maA1p_t7_peTBZM.r60jOf8J5Y7lkFc8xBxtNl9yoC6jZA.SWPFQWHjLMzj0pq2CHJUBA", + id="alg: A128KW, enc: A256GCM", + ), + ) + + JWE_192_BIT_OCT_PACKAGES = ( + pytest.param( + b"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTE5MktXIn0.RAXHkAPR_VsfFC5JAB0j24t_GdWa9udWTZZ_L18KE-qi9Au95oK-VA.J4YETSJp_EuV4AP0tWGIpw.VktlsPA1yF51IDXVtkrkmgHqPahz5-MjwAjCP0j3_EA.5h57BdovPem9fmyx-UcURA", + id="alg: A192KW, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTE5MktXIn0.Z2AmR2Y6viywyGDPPO92V5MJCwfULSRGmeSjV4VHqEnVyUE-AJhKety8Kw5dS_ydWVpZ0IGe4S0.Ny9jR93JsAigFdJXrcb1hQ.jwvtxfGZC3O6P8lBFUSb5OTRLFVje6Fo1H0X5F4uv1w.p14y3-XZHA3FiFSvXdbTsaFkylbwIKn3", + id="alg: A192KW, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTE5MktXIn0.wWChMcIkEaDfoMNfgsG1pomBzef0PYx_dJIe4V4JWeCS8RhH6_IzUb-zsgvyDUtKGeUHcwwQ66mpKnQO27-5p7cv6Geho9mq.5PcNZjsulZ3fTLu_NlQF2g.lXibtdYC3GsIiEtzkHqnOKu5uPrp6Fs8cdrakjZzQ4E.EuxTWElqFsG3lF4iJSGlzQKb3NXppEWQhWcpMOepjJE", + id="alg: A192KW, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTE5MktXIn0.acwCCSF6htimS4JReQVQii4RDwq9HD5a.JMdpDaFlJMMjm_Cz.tEkn2o4ngBafL16ldPcdR0VWhphi2w.3GpPpXKYtbPKzE6kTLtKEA", + id="alg: A192KW, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTE5MktXIn0.wXPSpLsBCaM0pBe9bFgUz-W0FLyAmGeRBI_VWD19rmU.XoiqwULpsnNvhwNo.sB3yhTbfBfWo7nbz8ZzLMX-RKzvrQw.Dn4XlsQpEjrf3mrjQ7sT5Q", + id="alg: A192KW, enc: A192GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTE5MktXIn0.DlGLalCXypefklVSenCRDRocRhHd3OI5vuAsxqTdDuVAks4PGbSkdw.zKpteENM-uElbKXK.cGvn4ozoLauLx-d7oEMJaLu-LttauA.1XOWJ6jZaxHWG13MUSPRAQ", + id="alg: A192KW, enc: A256GCM", + ), + ) + + JWE_256_BIT_OCT_PACKAGES = ( + pytest.param( + b"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTI1NktXIn0.XoNzh3DeJkShGkoZUlIHN6OHiA7ku5WzI_e9HvddWf-W6ygXfjiS8g.qUSQp7nyMReRRgGfw_VYmg.rrsoeZ_IecEkOAwOLyXWAo8uATnevhQJnIG4Gs-xUX8.05BaSh2pSaowV2omCOUdrw", + id="alg: A256KW, enc: A128CBC-HS256", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiYWxnIjoiQTI1NktXIn0.b2-ui_1ksCzR28fUnBqtfwhKJZxklXboiN6AkhiDlOuj54lrn5CcHCjOOj_p5TwYWrFIEV3cQqw.zZqUrF5ygGZ27kPqWsx1bg.qAgz0LaznF_uyh4k37DesB0k5im-GwC9Au7l0dXVdhI.guaip_HKbIHbKZJCVXSKjcNv40w5aYZQ", + id="alg: A256KW, enc: A192CBC-HS384", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYWxnIjoiQTI1NktXIn0.325MvraC58qFxXdD1gjRMwM_1NTW1-517eOckhcWuDUeAEUm6AHM9y1UsyC3StCDgFzDWbIZe3fayLh7OqVilr31gdofBWI9.hN1R-yoBJzALfcVFUvdKkQ.n-bQyooo7ufWn1CETJ8YFy9BFGWNgggrgoDlhmGI_Y8.6VyiR7w1osq6T8_rR-BAvyKAWAQSSA3oEc4jOPO7iJw", + id="alg: A256KW, enc: A256CBC-HS512", + ), + pytest.param( + b"eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTI1NktXIn0.d4wqGBFQG-MrzDgbWWB23o9LUgCkaTYt.NkFLhQfcR2swvLT3.lrt2LS9nrUqB9BDahJLqR-DZxutraA.MbghLfohCD71xfX8lRpVAw", + id="alg: A256KW, enc: A128GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiQTI1NktXIn0.XhgSqN16cCtwttRTxRXJfYx6FL9c56Bjo6VQx8E6vGI.C--W8_faFWiCxTCM.ZyTZRiqdLEMOnwQytgAujl-t6nZ-ZQ.GqyRs7YnGsGlwUehCXmllA", + id="alg: A256KW, enc: A192GCM", + ), + pytest.param( + b"eyJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiQTI1NktXIn0.i_-ehDezyG89YFcWqU-MxPB1HtVHauEAUGInnjlodx44IJBLS4ap4Q.nCEooStwaMWLfDxt.lqjEVnCRHCaufTIcxT2MzeBwUE2V-Q.sIr7c2QlWIYSVwnXUHgITA", + id="alg: A256KW, enc: A256GCM", + ), + ) + + @pytest.mark.parametrize("jwe_package", JWE_RSA_PACKAGES) + def test_decrypt_rsa_key_wrap(self, jwe_package): + headers = jwe.get_unverified_header(jwe_package) + if headers["alg"] not in ALGORITHMS.SUPPORTED: + pytest.skip("alg {} not supported".format(headers["alg"])) + if headers["enc"] not in ALGORITHMS.SUPPORTED: + pytest.skip("enc {} not supported".format(headers["enc"])) + key = PRIVATE_KEY_PEM + actual = jwe.decrypt(jwe_package, key) + assert actual == b"Live long and prosper." + + @pytest.mark.parametrize("jwe_package", JWE_128_BIT_OCT_PACKAGES) + def test_decrypt_oct_128_key_wrap(self, jwe_package): + key = OCT_128_BIT_KEY + headers = jwe.get_unverified_header(jwe_package) + if headers["alg"] not in ALGORITHMS.SUPPORTED: + pytest.skip("alg {} not supported".format(headers["alg"])) + if headers["enc"] not in ALGORITHMS.SUPPORTED: + pytest.skip("enc {} not supported".format(headers["enc"])) + actual = jwe.decrypt(jwe_package, key) + assert actual == b"Live long and prosper." + + @pytest.mark.parametrize("jwe_package", JWE_192_BIT_OCT_PACKAGES) + def test_decrypt_oct_192_key_wrap(self, jwe_package): + headers = jwe.get_unverified_header(jwe_package) + if headers["alg"] not in ALGORITHMS.SUPPORTED: + pytest.skip("alg {} not supported".format(headers["alg"])) + if headers["enc"] not in ALGORITHMS.SUPPORTED: + pytest.skip("enc {} not supported".format(headers["enc"])) + key = OCT_192_BIT_KEY + actual = jwe.decrypt(jwe_package, key) + assert actual == b"Live long and prosper." + + @pytest.mark.parametrize("jwe_package", JWE_256_BIT_OCT_PACKAGES) + def test_decrypt_oct_256_key_wrap(self, jwe_package): + headers = jwe.get_unverified_header(jwe_package) + if headers["alg"] not in ALGORITHMS.SUPPORTED: + pytest.skip("alg {} not supported".format(headers["alg"])) + if headers["enc"] not in ALGORITHMS.SUPPORTED: + pytest.skip("enc {} not supported".format(headers["enc"])) + key = OCT_256_BIT_KEY + actual = jwe.decrypt(jwe_package, key) + assert actual == b"Live long and prosper." + + def test_invalid_jwe_is_parse_error(self): + with pytest.raises(JWEParseError): + jwe.decrypt("invalid", "key") + + def test_non_json_header_is_parse_error(self): + jwe_str = ( + "ciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." + "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7" + "Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgN" + "Z__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRir" + "b6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8" + "OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0m" + "cKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" + "." + "AxY8DCtDaGlsbGljb3RoZQ." + "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." + "9hH0vgRfYgPnAHOd8stkvw" + ) + with pytest.raises(JWEParseError): + jwe.decrypt(jwe_str, "key") + + +class TestEncrypt: + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_rfc7516_appendix_b_direct(self, monkeypatch): + algorithm = ALGORITHMS.DIR + encryption = ALGORITHMS.A128CBC_HS256 + key = bytes( + bytearray( + [ + 4, + 211, + 31, + 197, + 84, + 157, + 252, + 254, + 11, + 100, + 157, + 250, + 63, + 170, + 106, + 206, + 107, + 124, + 212, + 45, + 111, + 107, + 9, + 219, + 200, + 177, + 0, + 240, + 143, + 156, + 44, + 207, + ] + ) + ) + plain_text = b"Live long and prosper." + expected_iv = bytes(bytearray([3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101])) + + for backend in backends: + monkeypatch.setattr(backend, "get_random_bytes", lambda x: expected_iv if x == 16 else key) + + expected = b"eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..AxY8DCtDaGlsbGljb3RoZQ.KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY.BIiCkt8mWOVyJOqDMwNqaQ" + actual = jwe.encrypt(plain_text, key, encryption, algorithm) + + assert actual == expected + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + @pytest.mark.parametrize("alg", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.RSA_KW)) + @pytest.mark.parametrize("enc", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.AES_ENC)) + @pytest.mark.parametrize("zip", ZIPS.SUPPORTED) + def test_encrypt_decrypt_rsa_kw(self, alg, enc, zip): + expected = b"Live long and prosper." + jwe_value = jwe.encrypt(expected[:], PUBLIC_KEY_PEM, enc, alg, zip) + actual = jwe.decrypt(jwe_value, PRIVATE_KEY_PEM) + assert actual == expected + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + @pytest.mark.parametrize("alg", ALGORITHMS.AES_KW) + @pytest.mark.parametrize("enc", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.AES_ENC)) + @pytest.mark.parametrize("zip", ZIPS.SUPPORTED) + def test_encrypt_decrypt_aes_kw(self, alg, enc, zip): + if alg == ALGORITHMS.A128KW: + key = OCT_128_BIT_KEY + elif alg == ALGORITHMS.A192KW: + key = OCT_192_BIT_KEY + elif alg == ALGORITHMS.A256KW: + key = OCT_256_BIT_KEY + else: + pytest.fail(f"I don't know how to handle enc {alg}") + expected = b"Live long and prosper." + jwe_value = jwe.encrypt(expected[:], key, enc, alg, zip) + actual = jwe.decrypt(jwe_value, key) + assert actual == expected + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + @pytest.mark.parametrize("enc", filter(lambda x: x in ALGORITHMS.SUPPORTED, ALGORITHMS.AES_ENC)) + @pytest.mark.parametrize("zip", ZIPS.SUPPORTED) + def test_encrypt_decrypt_dir_kw(self, enc, zip): + if enc == ALGORITHMS.A128GCM: + key = OCT_128_BIT_KEY + elif enc == ALGORITHMS.A192GCM: + key = OCT_192_BIT_KEY + elif enc in (ALGORITHMS.A128CBC_HS256, ALGORITHMS.A256GCM): + key = OCT_256_BIT_KEY + elif enc == ALGORITHMS.A192CBC_HS384: + key = OCT_384_BIT_KEY + elif enc == ALGORITHMS.A256CBC_HS512: + key = OCT_512_BIT_KEY + else: + pytest.fail(f"I don't know how to handle enc {enc}") + expected = b"Live long and prosper." + jwe_value = jwe.encrypt(expected[:], key, enc, ALGORITHMS.DIR, zip) + actual = jwe.decrypt(jwe_value, key) + assert actual == expected + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_alg_enc_headers(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert header["enc"] == enc + assert header["alg"] == alg + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_cty_header_present_when_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg, cty="expected") + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert header["cty"] == "expected" + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_cty_header_not_present_when_not_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert "cty" not in header + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_zip_header_present_when_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt(b"Text", PUBLIC_KEY_PEM, enc, alg, zip=ZIPS.DEF) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert header["zip"] == ZIPS.DEF + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_zip_header_not_present_when_not_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt(b"Text", PUBLIC_KEY_PEM, enc, alg) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert "zip" not in header + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_zip_header_not_present_when_none(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg, zip=ZIPS.NONE) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert "zip" not in header + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_kid_header_present_when_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg, kid="expected") + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert header["kid"] == "expected" + + @pytest.mark.skipif(AESKey is None, reason="No AES backend") + def test_kid_header_not_present_when_not_provided(self): + enc = ALGORITHMS.A256CBC_HS512 + alg = ALGORITHMS.RSA_OAEP_256 + encrypted = jwe.encrypt("Text", PUBLIC_KEY_PEM, enc, alg) + header = json.loads(base64url_decode(encrypted.split(b".")[0])) + assert "kid" not in header diff --git a/tests/test_jwk.py b/tests/test_jwk.py index 6ea5a1b..4d0b6ab 100644 --- a/tests/test_jwk.py +++ b/tests/test_jwk.py @@ -1,137 +1,144 @@ +import pytest + from jose import jwk -from jose.exceptions import JWKError +from jose.backends import AESKey, ECKey, HMACKey, RSAKey from jose.backends.base import Key -from jose.backends import ECKey, RSAKey - -import pytest +from jose.exceptions import JWKError hmac_key = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", - "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", } rsa_key = { "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" + "e": "AQAB", } ec_key = { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", - "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" + "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", } class TestJWK: - def test_interface(self): key = jwk.Key("key", "ALG") with pytest.raises(NotImplementedError): - key.sign('') + key.sign("") with pytest.raises(NotImplementedError): - key.verify('', '') + key.verify("", "") + @pytest.mark.skipif(RSAKey is None, reason="RSA is not available") def test_invalid_hash_alg(self): with pytest.raises(JWKError): - key = jwk.HMACKey(hmac_key, 'RS512') + key = HMACKey(hmac_key, "RS512") with pytest.raises(JWKError): - key = RSAKey(rsa_key, 'HS512') + key = RSAKey(rsa_key, "HS512") with pytest.raises(JWKError): - key = ECKey(ec_key, 'RS512') # noqa: F841 + key = ECKey(ec_key, "RS512") # noqa: F841 + @pytest.mark.skipif(RSAKey is None, reason="RSA is not available") def test_invalid_jwk(self): with pytest.raises(JWKError): - key = jwk.HMACKey(rsa_key, 'HS256') + key = HMACKey(rsa_key, "HS256") with pytest.raises(JWKError): - key = RSAKey(hmac_key, 'RS256') + key = RSAKey(hmac_key, "RS256") with pytest.raises(JWKError): - key = ECKey(rsa_key, 'ES256') # noqa: F841 + key = ECKey(rsa_key, "ES256") # noqa: F841 + @pytest.mark.skipif(RSAKey is None, reason="RSA is not available") def test_RSAKey_errors(self): rsa_key = { "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" + "e": "AQAB", } with pytest.raises(JWKError): - key = RSAKey(rsa_key, 'HS256') + key = RSAKey(rsa_key, "HS256") rsa_key = { "kty": "oct", "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" + "e": "AQAB", } with pytest.raises(JWKError): - key = RSAKey(rsa_key, 'RS256') # noqa: F841 + key = RSAKey(rsa_key, "RS256") # noqa: F841 def test_construct_from_jwk(self): hmac_key = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", - "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", } key = jwk.construct(hmac_key) assert isinstance(key, jwk.Key) def test_construct_EC_from_jwk(self): - key = ECKey(ec_key, algorithm='ES512') + key = ECKey(ec_key, algorithm="ES512") assert isinstance(key, jwk.Key) def test_construct_from_jwk_missing_alg(self): hmac_key = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", - "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", } with pytest.raises(JWKError): key = jwk.construct(hmac_key) with pytest.raises(JWKError): key = jwk.construct("key", algorithm="NONEXISTENT") # noqa: F841 def test_get_key(self): hs_key = jwk.get_key("HS256") - assert hs_key == jwk.HMACKey + assert hs_key == HMACKey assert issubclass(hs_key, Key) - assert issubclass(jwk.get_key("RS256"), Key) + if RSAKey is not None: + assert issubclass(jwk.get_key("RS256"), Key) assert issubclass(jwk.get_key("ES256"), Key) assert jwk.get_key("NONEXISTENT") is None + @pytest.mark.skipif(AESKey is None, reason="No AES provider") + def test_get_aes_key(self): + assert issubclass(jwk.get_key("A256CBC-HS512"), Key) + def test_register_key(self): assert jwk.register_key("ALG", jwk.Key) assert jwk.get_key("ALG") == jwk.Key with pytest.raises(TypeError): assert jwk.register_key("ALG", object) diff --git a/tests/test_jws.py b/tests/test_jws.py index f543a03..01b5fd0 100644 --- a/tests/test_jws.py +++ b/tests/test_jws.py @@ -1,346 +1,396 @@ import json +import warnings -from jose import jwk -from jose import jws +import pytest + +from jose import jwk, jws +from jose.backends import RSAKey from jose.constants import ALGORITHMS from jose.exceptions import JWSError -import pytest +try: + from jose.backends.cryptography_backend import CryptographyRSAKey +except ImportError: + CryptographyRSAKey = None @pytest.fixture def payload(): payload = b"test payload" return payload -class TestJWS(object): - +class TestJWS: def test_unicode_token(self): - token = u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' - jws.verify(token, 'secret', ['HS256']) + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" + jws.verify(token, "secret", ["HS256"]) def test_multiple_keys(self): old_jwk_verify = jwk.HMACKey.verify try: - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" def raise_exception(self, msg, sig): - if self.prepared_key == b'incorrect': + if self.prepared_key == b"incorrect": raise Exception("Mocked function jose.jwk.HMACKey.verify") else: return True jwk.HMACKey.verify = raise_exception - jws.verify(token, {'keys': ['incorrect', 'secret']}, ['HS256']) + jws.verify(token, {"keys": ["incorrect", "secret"]}, ["HS256"]) finally: jwk.HMACKey.verify = old_jwk_verify def test_invalid_algorithm(self): - token = u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', [None]) + jws.verify(token, "secret", [None]) def test_not_enough_segments(self): - token = 'eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_header_invalid_padding(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9A.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9A.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_header_not_json(self): - token = 'dGVzdA.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "dGVzdA.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_claims_invalid_padding(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.AeyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.AeyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_claims_not_json(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.dGVzdA.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.dGVzdA.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_invalid_key(self, payload): with pytest.raises(JWSError): - jws.sign(payload, 'secret', algorithm='RS256') - - -class TestHMAC(object): - + jws.sign(payload, "secret", algorithm="RS256") + + @pytest.mark.parametrize( + "key", + [ + b"key", + "key", + ], + ) + def test_round_trip_with_different_key_types(self, key): + signed_data = jws.sign({"testkey": "testvalue"}, key, algorithm=ALGORITHMS.HS256) + verified_bytes = jws.verify(signed_data, key, algorithms=[ALGORITHMS.HS256]) + verified_data = json.loads(verified_bytes.decode("utf-8")) + assert "testkey" in verified_data.keys() + assert verified_data["testkey"] == "testvalue" + + +class TestJWK: + def test_jwk(self, payload): + key_data = "key" + key = jwk.construct(key_data, algorithm="HS256") + token = jws.sign(payload, key, algorithm=ALGORITHMS.HS256) + assert jws.verify(token, key_data, ALGORITHMS.HS256) == payload + + +class TestHMAC: def testHMAC256(self, payload): - token = jws.sign(payload, 'secret', algorithm=ALGORITHMS.HS256) - assert jws.verify(token, 'secret', ALGORITHMS.HS256) == payload + token = jws.sign(payload, "secret", algorithm=ALGORITHMS.HS256) + assert jws.verify(token, "secret", ALGORITHMS.HS256) == payload def testHMAC384(self, payload): - token = jws.sign(payload, 'secret', algorithm=ALGORITHMS.HS384) - assert jws.verify(token, 'secret', ALGORITHMS.HS384) == payload + token = jws.sign(payload, "secret", algorithm=ALGORITHMS.HS384) + assert jws.verify(token, "secret", ALGORITHMS.HS384) == payload def testHMAC512(self, payload): - token = jws.sign(payload, 'secret', algorithm=ALGORITHMS.HS512) - assert jws.verify(token, 'secret', ALGORITHMS.HS512) == payload + token = jws.sign(payload, "secret", algorithm=ALGORITHMS.HS512) + assert jws.verify(token, "secret", ALGORITHMS.HS512) == payload def test_wrong_alg(self, payload): - token = jws.sign(payload, 'secret', algorithm=ALGORITHMS.HS256) + token = jws.sign(payload, "secret", algorithm=ALGORITHMS.HS256) with pytest.raises(JWSError): - jws.verify(token, 'secret', ALGORITHMS.HS384) + jws.verify(token, "secret", ALGORITHMS.HS384) def test_wrong_key(self, payload): - token = jws.sign(payload, 'secret', algorithm=ALGORITHMS.HS256) + token = jws.sign(payload, "secret", algorithm=ALGORITHMS.HS256) with pytest.raises(JWSError): - jws.verify(token, 'another', ALGORITHMS.HS256) + jws.verify(token, "another", ALGORITHMS.HS256) def test_unsupported_alg(self, payload): with pytest.raises(JWSError): - jws.sign(payload, 'secret', algorithm='SOMETHING') + jws.sign(payload, "secret", algorithm="SOMETHING") def test_add_headers(self, payload): - additional_headers = { - 'test': 'header' - } + additional_headers = {"test": "header"} expected_headers = { - 'test': 'header', - 'alg': 'HS256', - 'typ': 'JWT', + "test": "header", + "alg": "HS256", + "typ": "JWT", } - token = jws.sign(payload, 'secret', headers=additional_headers) + token = jws.sign(payload, "secret", headers=additional_headers) header, payload, signing_input, signature = jws._load(token) assert expected_headers == header rsa_private_key = """-----BEGIN RSA PRIVATE KEY----- MIIJKwIBAAKCAgEAtSKfSeI0fukRIX38AHlKB1YPpX8PUYN2JdvfM+XjNmLfU1M7 4N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/KgBZggAlS9Y0Vx8DsSL2HvOjguAdX ir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy/38+1r17/cYTp76brKpU1I4kM20M //dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2oaQFww/XHGz69Q7yHK6DbxYO4w4q2 sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/mhhRZLU5aynQpwaVv2U++CL6EvGt 8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKlXhMGT619v82LneTdsqA25Wi2Ld/c 0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaHXE1SLpLPoIp8uppGF02Nz2v3ld8g CnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6MloRDy4na0pRQv61VogqRKDU2r3/V ezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q5R/qQGmc6BYtfk5rn7iIfXlkJAZH XhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09Xcppx7kdwsJy72Sust9Hnd9B7V35 YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2Ks3IHH7tVltM6NsRk3jNdVMCAwEA AQKCAgEArx+0JXigDHtFZr4pYEPjwMgCBJ2dr8+L8PptB/4g+LoK9MKqR7M4aTO+ PoILPXPyWvZq/meeDakyZLrcdc8ad1ArKF7baDBpeGEbkRA9JfV5HjNq/ea4gyvD MCGou8ZPSQCnkRmr8LFQbJDgnM5Za5AYrwEv2aEh67IrTHq53W83rMioIumCNiG+ 7TQ7egEGiYsQ745GLrECLZhKKRTgt/T+k1cSk1LLJawme5XgJUw+3D9GddJEepvY oL+wZ/gnO2ADyPnPdQ7oc2NPcFMXpmIQf29+/g7FflatfQhkIv+eC6bB51DhdMi1 zyp2hOhzKg6jn74ixVX+Hts2/cMiAPu0NaWmU9n8g7HmXWc4+uSO/fssGjI3DLYK d5xnhrq4a3ZO5oJLeMO9U71+Ykctg23PTHwNAGrsPYdjGcBnJEdtbXa31agI5PAG 6rgGUY3iSoWqHLgBTxrX04TWVvLQi8wbxh7BEF0yasOeZKxdE2IWYg75zGsjluyH lOnpRa5lSf6KZ6thh9eczFHYtS4DvYBcZ9hZW/g87ie28SkBFxxl0brYt9uKNYJv uajVG8kT80AC7Wzg2q7Wmnoww3JNJUbNths5dqKyUSlMFMIB/vOePFHLrA6qDfAn sQHgUb9WHhUrYsH20XKpqR2OjmWU05bV4pSMW/JwG37o+px1yKECggEBANnwx0d7 ksEMvJjeN5plDy3eMLifBI+6SL/o5TXDoFM6rJxF+0UP70uouYJq2dI+DCSA6c/E sn7WAOirY177adKcBV8biwAtmKHnFnCs/kwAZq8lMvQPtNPJ/vq2n40kO48h8fxb eGcmyAqFPZ4YKSxrPA4cdbHIuFSt9WyaUcVFmzdTFHVlRP70EXdmXHt84byWNB4C Heq8zmrNxPNAi65nEkUks7iBQMtuvyV2+aXjDOTBMCd66IhIh2iZq1O7kXUwgh1O H9hCa7oriHyAdgkKdKCWocmbPPENOETgjraA9wRIXwOYTDb1X5hMvi1mCHo8xjMj u4szD03xJVi7WrsCggEBANTEblCkxEyhJqaMZF3U3df2Yr/ZtHqsrTr4lwB/MOKk zmuSrROxheEkKIsxbiV+AxTvtPR1FQrlqbhTJRwy+pw4KPJ7P4fq2R/YBqvXSNBC amTt6l2XdXqnAk3A++cOEZ2lU9ubfgdeN2Ih8rgdn1LWeOSjCWfExmkoU61/Xe6x AMeXKQSlHKSnX9voxuE2xINHeU6ZAKy1kGmrJtEiWnI8b8C4s8fTyDtXJ1Lasys0 iHO2Tz2jUhf4IJwb87Lk7Ize2MrI+oPzVDXlmkbjkB4tYyoiRTj8rk8pwBW/HVv0 02pjOLTa4kz1kQ3lsZ/3As4zfNi7mWEhadmEsAIfYkkCggEBANO39r/Yqj5kUyrm ZXnVxyM2AHq58EJ4I4hbhZ/vRWbVTy4ZRfpXeo4zgNPTXXvCzyT/HyS53vUcjJF7 PfPdpXX2H7m/Fg+8O9S8m64mQHwwv5BSQOecAnzkdJG2q9T/Z+Sqg1w2uAbtQ9QE kFFvA0ClhBfpSeTGK1wICq3QVLOh5SGf0fYhxR8wl284v4svTFRaTpMAV3Pcq2JS N4xgHdH1S2hkOTt6RSnbklGg/PFMWxA3JMKVwiPy4aiZ8DhNtQb1ctFpPcJm9CRN ejAI06IAyD/hVZZ2+oLp5snypHFjY5SDgdoKL7AMOyvHEdEkmAO32ot/oQefOLTt GOzURVUCggEBALSx5iYi6HtT2SlUzeBKaeWBYDgiwf31LGGKwWMwoem5oX0GYmr5 NwQP20brQeohbKiZMwrxbF+G0G60Xi3mtaN6pnvYZAogTymWI4RJH5OO9CCnVYUK nkD+GRzDqqt97UP/Joq5MX08bLiwsBvhPG/zqVQzikdQfFjOYNJV+wY92LWpELLb Lso/Q0/WDyExjA8Z4lH36vTCddTn/91Y2Ytu/FGmCzjICaMrzz+0cLlesgvjZsSo MY4dskQiEQN7G9I/Z8pAiVEKlBf52N4fYUPfs/oShMty/O5KPNG7L0nrUKlnfr9J rStC2l/9FK8P7pgEbiD6obY11FlhMMF8udECggEBAIKhvOFtipD1jqDOpjOoR9sK /lRR5bVVWQfamMDN1AwmjJbVHS8hhtYUM/4sh2p12P6RgoO8fODf1vEcWFh3xxNZ E1pPCPaICD9i5U+NRvPz2vC900HcraLRrUFaRzwhqOOknYJSBrGzW+Cx3YSeaOCg nKyI8B5gw4C0G0iL1dSsz2bR1O4GNOVfT3R6joZEXATFo/Kc2L0YAvApBNUYvY0k bjJ/JfTO5060SsWftf4iw3jrhSn9RwTTYdq/kErGFWvDGJn2MiuhMe2onNfVzIGR mdUxHwi1ulkspAn/fmY7f0hZpskDwcHyZmbKZuk+NU/FJ8IAcmvk9y7m25nSSc8= -----END RSA PRIVATE KEY-----""" rsa_public_key = """-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtSKfSeI0fukRIX38AHlK B1YPpX8PUYN2JdvfM+XjNmLfU1M74N0VmdzIX95sneQGO9kC2xMIE+AIlt52Yf/K gBZggAlS9Y0Vx8DsSL2HvOjguAdXir3vYLvAyyHin/mUisJOqccFKChHKjnk0uXy /38+1r17/cYTp76brKpU1I4kM20M//dbvLBWjfzyw9ehufr74aVwr+0xJfsBVr2o aQFww/XHGz69Q7yHK6DbxYO4w4q2sIfcC4pT8XTPHo4JZ2M733Ea8a7HxtZS563/ mhhRZLU5aynQpwaVv2U++CL6EvGt8TlNZOkeRv8wz+Rt8B70jzoRpVK36rR+pHKl XhMGT619v82LneTdsqA25Wi2Ld/c0niuul24A6+aaj2u9SWbxA9LmVtFntvNbRaH XE1SLpLPoIp8uppGF02Nz2v3ld8gCnTTWfq/BQ80Qy8e0coRRABECZrjIMzHEg6M loRDy4na0pRQv61VogqRKDU2r3/VezFPQDb3ciYsZjWBr3HpNOkUjTrvLmFyOE9Q 5R/qQGmc6BYtfk5rn7iIfXlkJAZHXhBy+ElBuiBM+YSkFM7dH92sSIoZ05V4MP09 Xcppx7kdwsJy72Sust9Hnd9B7V35YnVF6W791lVHnenhCJOziRmkH4xLLbPkaST2 Ks3IHH7tVltM6NsRk3jNdVMCAwEAAQ== -----END PUBLIC KEY-----""" @pytest.fixture def jwk_set(): - return {u'keys': [{u'alg': u'RS256', - u'e': u'AQAB', - u'kid': u'40aa42edac0614d7ca3f57f97ee866cdfba3b61a', - u'kty': u'RSA', - u'n': u'6lm9AEGLPFpVqnfeVFuTIZsj7vz_kxla6uW1WWtosM_MtIjXkyyiSolxiSOs3bzG66iVm71023QyOzKYFbio0hI-yZauG3g9nH-zb_AHScsjAKagHtrHmTdtq0JcNkQnAaaUwxVbjwMlYAcOh87W5jWj_MAcPvc-qjy8-WJ81UgoOUZNiKByuF4-9igxKZeskGRXuTPX64kWGBmKl-tM7VnCGMKoK3m92NPrktfBoNN_EGGthNfQsKFUdQFJFtpMuiXp9Gib7dcMGabxcG2GUl-PU086kPUyUdUYiMN2auKSOxSUZgDjT7DcI8Sn8kdQ0-tImaHi54JNa1PNNdKRpw', - u'use': u'sig'}, - {u'alg': u'RS256', - u'e': u'AQAB', - u'kid': u'8fbbeea40332d2c0d27e37e1904af29b64594e57', - u'kty': u'RSA', - u'n': u'z7h6_rt35-j6NV2iQvYIuR3xvsxmEImgMl8dc8CFl4SzEWrry3QILajKxQZA9YYYfXIcZUG_6R6AghVMJetNIl2AhCoEr3RQjjNsm9PE6h5p2kQ-zIveFeb__4oIkVihYtxtoYBSdVj69nXLUAJP2bxPfU8RDp5X7hT62pKR05H8QLxH8siIQ5qR2LGFw_dJcitAVRRQofuaj_9u0CLZBfinqyRkBc7a0zi7pBxtEiIbn9sRr8Kkb_Boap6BHbnLS-YFBVarcgFBbifRf7NlK5dqE9z4OUb-dx8wCMRIPVAx_hV4Qx2anTgp1sDA6V4vd4NaCOZX-mSctNZqQmKtNw', - u'use': u'sig'}, - {u'alg': u'RS256', - u'e': u'AQAB', - u'kid': u'6758b0b8eb341e90454860432d6a1648bf4de03b', - u'kty': u'RSA', - u'n': u'5K0rYaA7xtqSe1nFn_nCA10uUXY81NcohMeFsYLbBlx_NdpsmbpgtXJ6ektYR7rUdtMMLu2IONlNhkWlx-lge91okyacUrWHP88PycilUE-RnyVjbPEm3seR0VefgALfN4y_e77ljq2F7W2_kbUkTvDzriDIWvQT0WwVF5FIOBydfDDs92S-queaKgLBwt50SXJCZryLew5ODrwVsFGI4Et6MLqjS-cgWpCNwzcRqjBRsse6DXnex_zSRII4ODzKIfX4qdFBKZHO_BkTsK9DNkUayrr9cz8rFRK6TEH6XTVabgsyd6LP6PTxhpiII_pTYRSWk7CGMnm2nO0dKxzaFQ', - u'use': u'sig'}]} + return { + "keys": [ + { + "alg": "RS256", + "e": "AQAB", + "kid": "40aa42edac0614d7ca3f57f97ee866cdfba3b61a", + "kty": "RSA", + "n": "6lm9AEGLPFpVqnfeVFuTIZsj7vz_kxla6uW1WWtosM_MtIjXkyyiSolxiSOs3bzG66iVm71023QyOzKYFbio0hI-yZauG3g9nH-zb_AHScsjAKagHtrHmTdtq0JcNkQnAaaUwxVbjwMlYAcOh87W5jWj_MAcPvc-qjy8-WJ81UgoOUZNiKByuF4-9igxKZeskGRXuTPX64kWGBmKl-tM7VnCGMKoK3m92NPrktfBoNN_EGGthNfQsKFUdQFJFtpMuiXp9Gib7dcMGabxcG2GUl-PU086kPUyUdUYiMN2auKSOxSUZgDjT7DcI8Sn8kdQ0-tImaHi54JNa1PNNdKRpw", + "use": "sig", + }, + { + "alg": "RS256", + "e": "AQAB", + "kid": "8fbbeea40332d2c0d27e37e1904af29b64594e57", + "kty": "RSA", + "n": "z7h6_rt35-j6NV2iQvYIuR3xvsxmEImgMl8dc8CFl4SzEWrry3QILajKxQZA9YYYfXIcZUG_6R6AghVMJetNIl2AhCoEr3RQjjNsm9PE6h5p2kQ-zIveFeb__4oIkVihYtxtoYBSdVj69nXLUAJP2bxPfU8RDp5X7hT62pKR05H8QLxH8siIQ5qR2LGFw_dJcitAVRRQofuaj_9u0CLZBfinqyRkBc7a0zi7pBxtEiIbn9sRr8Kkb_Boap6BHbnLS-YFBVarcgFBbifRf7NlK5dqE9z4OUb-dx8wCMRIPVAx_hV4Qx2anTgp1sDA6V4vd4NaCOZX-mSctNZqQmKtNw", + "use": "sig", + }, + { + "alg": "RS256", + "e": "AQAB", + "kid": "6758b0b8eb341e90454860432d6a1648bf4de03b", + "kty": "RSA", + "n": "5K0rYaA7xtqSe1nFn_nCA10uUXY81NcohMeFsYLbBlx_NdpsmbpgtXJ6ektYR7rUdtMMLu2IONlNhkWlx-lge91okyacUrWHP88PycilUE-RnyVjbPEm3seR0VefgALfN4y_e77ljq2F7W2_kbUkTvDzriDIWvQT0WwVF5FIOBydfDDs92S-queaKgLBwt50SXJCZryLew5ODrwVsFGI4Et6MLqjS-cgWpCNwzcRqjBRsse6DXnex_zSRII4ODzKIfX4qdFBKZHO_BkTsK9DNkUayrr9cz8rFRK6TEH6XTVabgsyd6LP6PTxhpiII_pTYRSWk7CGMnm2nO0dKxzaFQ", + "use": "sig", + }, + ] + } google_id_token = ( - 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjhmYmJlZWE0MDMzMmQyYzBkMjdlMzdlMTkwN' - 'GFmMjliNjQ1OTRlNTcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5' - 'jb20iLCJhdF9oYXNoIjoiUUY5RnRjcHlmbUFBanJuMHVyeUQ5dyIsImF1ZCI6IjQw' - 'NzQwODcxODE5Mi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwN' - 'zkzMjQxNjk2NTIwMzIzNDA3NiIsImF6cCI6IjQwNzQwODcxODE5Mi5hcHBzLmdvb2' - 'dsZXVzZXJjb250ZW50LmNvbSIsImlhdCI6MTQ2ODYyMjQ4MCwiZXhwIjoxNDY4NjI' - '2MDgwfQ.Nz6VREh7smvfVRWNHlbKZ6W_DX57akRUGrDTcns06ndAwrslwUlBeFsWY' - 'RLon_tDw0QCeQCGvw7l1AT440UQBRP-mtqK_2Yny2JmIQ7Ll6UAIHRhXOD1uj9w5v' - 'X0jyI1MbjDtODeDWWn_9EDJRBd4xmwKhAONuWodTgSi7qGe1UVmzseFNNkKdoo54d' - 'XhCJiyiRAMnWB_FQDveRJghche131pd9O_E4Wj6hf_zCcMTaDaLDOmElcQe-WsKWA' - 'A3YwHFEWOLO_7x6u4uGmhItPGH7zsOTzYxPYhZMSZusgVg9fbE1kSlHVSyQrcp_rR' - 'WNz7vOIbvIlBR9Jrq5MIqbkkg' + "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhmYmJlZWE0MDMzMmQyYzBkMjdlMzdlMTkwN" + "GFmMjliNjQ1OTRlNTcifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5" + "jb20iLCJhdF9oYXNoIjoiUUY5RnRjcHlmbUFBanJuMHVyeUQ5dyIsImF1ZCI6IjQw" + "NzQwODcxODE5Mi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjEwN" + "zkzMjQxNjk2NTIwMzIzNDA3NiIsImF6cCI6IjQwNzQwODcxODE5Mi5hcHBzLmdvb2" + "dsZXVzZXJjb250ZW50LmNvbSIsImlhdCI6MTQ2ODYyMjQ4MCwiZXhwIjoxNDY4NjI" + "2MDgwfQ.Nz6VREh7smvfVRWNHlbKZ6W_DX57akRUGrDTcns06ndAwrslwUlBeFsWY" + "RLon_tDw0QCeQCGvw7l1AT440UQBRP-mtqK_2Yny2JmIQ7Ll6UAIHRhXOD1uj9w5v" + "X0jyI1MbjDtODeDWWn_9EDJRBd4xmwKhAONuWodTgSi7qGe1UVmzseFNNkKdoo54d" + "XhCJiyiRAMnWB_FQDveRJghche131pd9O_E4Wj6hf_zCcMTaDaLDOmElcQe-WsKWA" + "A3YwHFEWOLO_7x6u4uGmhItPGH7zsOTzYxPYhZMSZusgVg9fbE1kSlHVSyQrcp_rR" + "WNz7vOIbvIlBR9Jrq5MIqbkkg" ) -class TestGetKeys(object): - +class TestGetKeys: def test_dict(self): assert ({},) == jws._get_keys({}) def test_custom_object(self): class MyDict(dict): pass + mydict = MyDict() assert (mydict,) == jws._get_keys(mydict) def test_RFC7517_string(self): key = '{"keys": [{}, {}]}' assert [{}, {}] == jws._get_keys(key) def test_RFC7517_jwk(self): - key = {'kty': 'hsa', 'k': 'secret', 'alg': 'HS256', 'use': 'sig'} - assert (key, ) == jws._get_keys(key) + key = {"kty": "hsa", "k": "secret", "alg": "HS256", "use": "sig"} + assert (key,) == jws._get_keys(key) def test_RFC7517_mapping(self): key = {"keys": [{}, {}]} assert [{}, {}] == jws._get_keys(key) def test_string(self): - assert ('test',) == jws._get_keys('test') + assert ("test",) == jws._get_keys("test") def test_tuple(self): - assert ('test', 'key') == jws._get_keys(('test', 'key')) + assert ("test", "key") == jws._get_keys(("test", "key")) def test_list(self): - assert ['test', 'key'] == jws._get_keys(['test', 'key']) + assert ["test", "key"] == jws._get_keys(["test", "key"]) + def test_jwk(self): + jwkey = jwk.construct("key", algorithm="HS256") + assert (jwkey,) == jws._get_keys(jwkey) -class TestRSA(object): +@pytest.mark.skipif(RSAKey is None, reason="RSA is not available") +class TestRSA: def test_jwk_set(self, jwk_set): # Would raise a JWSError if validation failed. payload = jws.verify(google_id_token, jwk_set, ALGORITHMS.RS256) - iss = json.loads(payload.decode('utf-8'))['iss'] + iss = json.loads(payload.decode("utf-8"))["iss"] assert iss == "https://accounts.google.com" def test_jwk_set_failure(self, jwk_set): # Remove the key that was used to sign this token. - del jwk_set['keys'][1] + del jwk_set["keys"][1] with pytest.raises(JWSError): payload = jws.verify(google_id_token, jwk_set, ALGORITHMS.RS256) # noqa: F841 def test_RSA256(self, payload): token = jws.sign(payload, rsa_private_key, algorithm=ALGORITHMS.RS256) assert jws.verify(token, rsa_public_key, ALGORITHMS.RS256) == payload def test_RSA384(self, payload): token = jws.sign(payload, rsa_private_key, algorithm=ALGORITHMS.RS384) assert jws.verify(token, rsa_public_key, ALGORITHMS.RS384) == payload def test_RSA512(self, payload): token = jws.sign(payload, rsa_private_key, algorithm=ALGORITHMS.RS512) assert jws.verify(token, rsa_public_key, ALGORITHMS.RS512) == payload def test_wrong_alg(self, payload): token = jws.sign(payload, rsa_private_key, algorithm=ALGORITHMS.RS256) with pytest.raises(JWSError): jws.verify(token, rsa_public_key, ALGORITHMS.RS384) def test_wrong_key(self, payload): token = jws.sign(payload, rsa_private_key, algorithm=ALGORITHMS.RS256) with pytest.raises(JWSError): jws.verify(token, rsa_public_key, ALGORITHMS.HS256) + def test_private_verify_raises_warning(self, payload): + token = jws.sign(payload, rsa_private_key, algorithm="RS256") + + # verify with public + jws.verify(token, rsa_public_key, algorithms="RS256") + + with warnings.catch_warnings(record=True) as w: + # verify with private raises warning + jws.verify(token, rsa_private_key, algorithms="RS256") + + assert ("Attempting to verify a message with a private key. " "This is not recommended.") == str( + w[-1].message + ) + ec_private_key = """-----BEGIN EC PRIVATE KEY----- MIHcAgEBBEIBzs13YUnYbLfYXTz4SG4DE4rPmsL3wBTdy34JcO+BDpI+NDZ0pqam UM/1sGZT+8hqUjSeQo6oz+Mx0VS6SJh31zygBwYFK4EEACOhgYkDgYYABACYencK 8pm/iAeDVptaEZTZwNT0yW/muVwvvwkzS/D6GDCLsnLfI6e1FwEnTJF/GPFUlN5l 9JSLxsbbFdM1muI+NgBE6ZLR1GZWjsNzu7BOB8RMy/mvSTokZwyIaWvWSn3hOF4i /4iczJnzJhUKDqHe5dJ//PLd7R3WVHxkvv7jFNTKYg== -----END EC PRIVATE KEY-----""" ec_public_key = """-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAmHp3CvKZv4gHg1abWhGU2cDU9Mlv 5rlcL78JM0vw+hgwi7Jy3yOntRcBJ0yRfxjxVJTeZfSUi8bG2xXTNZriPjYAROmS 0dRmVo7Dc7uwTgfETMv5r0k6JGcMiGlr1kp94TheIv+InMyZ8yYVCg6h3uXSf/zy 3e0d1lR8ZL7+4xTUymI= -----END PUBLIC KEY-----""" -class TestEC(object): - +class TestEC: def test_EC256(self, payload): token = jws.sign(payload, ec_private_key, algorithm=ALGORITHMS.ES256) assert jws.verify(token, ec_public_key, ALGORITHMS.ES256) == payload def test_EC384(self, payload): token = jws.sign(payload, ec_private_key, algorithm=ALGORITHMS.ES384) assert jws.verify(token, ec_public_key, ALGORITHMS.ES384) == payload def test_EC512(self, payload): token = jws.sign(payload, ec_private_key, algorithm=ALGORITHMS.ES512) assert jws.verify(token, ec_public_key, ALGORITHMS.ES512) == payload def test_wrong_alg(self, payload): token = jws.sign(payload, ec_private_key, algorithm=ALGORITHMS.ES256) with pytest.raises(JWSError): jws.verify(token, rsa_public_key, ALGORITHMS.ES384) -class TestLoad(object): - +class TestLoad: def test_header_not_mapping(self): - token = 'WyJ0ZXN0Il0.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "WyJ0ZXN0Il0.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_claims_not_mapping(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.WyJ0ZXN0Il0.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.WyJ0ZXN0Il0.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) def test_signature_padding(self): - token = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8' + token = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9.aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" with pytest.raises(JWSError): - jws.verify(token, 'secret', ['HS256']) + jws.verify(token, "secret", ["HS256"]) diff --git a/tests/test_jwt.py b/tests/test_jwt.py index 03f5c28..2409f1f 100644 --- a/tests/test_jwt.py +++ b/tests/test_jwt.py @@ -1,604 +1,556 @@ - import base64 import json - -from jose import jws -from jose import jwt -from jose.exceptions import JWTError - -from datetime import datetime -from datetime import timedelta +from datetime import datetime, timedelta import pytest +from jose import jws, jwt +from jose.exceptions import JWTError + @pytest.fixture def claims(): - claims = { - 'a': 'b' - } + claims = {"a": "b"} return claims @pytest.fixture def key(): - return 'secret' + return "secret" @pytest.fixture def headers(): headers = { - 'kid': 'my-key-id', + "kid": "my-key-id", } return headers class TestJWT: - def test_no_alg(self, claims, key): - token = jwt.encode(claims, key, algorithm='HS384') - b64header, b64payload, b64signature = token.split('.') - header_json = base64.urlsafe_b64decode(b64header.encode('utf-8')) - header = json.loads(header_json.decode('utf-8')) - del header['alg'] - bad_header_json_bytes = json.dumps(header).encode('utf-8') + token = jwt.encode(claims, key, algorithm="HS384") + b64header, b64payload, b64signature = token.split(".") + header_json = base64.urlsafe_b64decode(b64header.encode("utf-8")) + header = json.loads(header_json.decode("utf-8")) + del header["alg"] + bad_header_json_bytes = json.dumps(header).encode("utf-8") bad_b64header_bytes = base64.urlsafe_b64encode(bad_header_json_bytes) - bad_b64header_bytes_short = bad_b64header_bytes.replace(b'=', b'') - bad_b64header = bad_b64header_bytes.decode('utf-8') - bad_token = '.'.join([bad_b64header, b64payload, b64signature]) + bad_b64header_bytes_short = bad_b64header_bytes.replace(b"=", b"") + bad_b64header = bad_b64header_bytes.decode("utf-8") + bad_token = ".".join([bad_b64header, b64payload, b64signature]) with pytest.raises(JWTError): - jwt.decode( - token=bad_token, - key=key, - algorithms=[]) + jwt.decode(token=bad_token, key=key, algorithms=[]) + + @pytest.mark.parametrize( + "key, token", + [ + ( + "1234567890", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.aNBlulVhiYSCzvsh1rTzXZC2eWJmNrMBjINT-0wQz4k", + ), + ( + "123456789.0", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.D8WLFPMi3yKgua2jm3BKThFsParXpgxhIbsUc39zJDw", + ), + ], + ) + def test_numeric_key(self, key, token): + token_info = jwt.decode(token, key) + assert token_info == {"name": "test"} def test_invalid_claims_json(self): old_jws_verify = jws.verify try: - token = u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" def return_invalid_json(token, key, algorithms, verify=True): return b'["a", "b"}' jws.verify = return_invalid_json - with pytest.raises(JWTError, match='Invalid payload string: '): - jwt.decode(token, 'secret', ['HS256']) + with pytest.raises(JWTError, match="Invalid payload string: "): + jwt.decode(token, "secret", ["HS256"]) finally: jws.verify = old_jws_verify def test_invalid_claims(self): old_jws_verify = jws.verify try: - token = u'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" def return_encoded_array(token, key, algorithms, verify=True): return b'["a","b"]' jws.verify = return_encoded_array - with pytest.raises(JWTError, match='Invalid payload string: must be a json object'): - jwt.decode(token, 'secret', ['HS256']) + with pytest.raises(JWTError, match="Invalid payload string: must be a json object"): + jwt.decode(token, "secret", ["HS256"]) finally: jws.verify = old_jws_verify def test_non_default_alg(self, claims, key): - encoded = jwt.encode(claims, key, algorithm='HS384') - decoded = jwt.decode(encoded, key, algorithms='HS384') + encoded = jwt.encode(claims, key, algorithm="HS384") + decoded = jwt.decode(encoded, key, algorithms="HS384") assert claims == decoded def test_non_default_alg_positional_bwcompat(self, claims, key): - encoded = jwt.encode(claims, key, 'HS384') - decoded = jwt.decode(encoded, key, 'HS384') + encoded = jwt.encode(claims, key, "HS384") + decoded = jwt.decode(encoded, key, "HS384") assert claims == decoded def test_no_alg_default_headers(self, claims, key, headers): - token = jwt.encode(claims, key, algorithm='HS384') - b64header, b64payload, b64signature = token.split('.') - bad_token = b64header + '.' + b64payload + token = jwt.encode(claims, key, algorithm="HS384") + b64header, b64payload, b64signature = token.split(".") + bad_token = b64header + "." + b64payload with pytest.raises(JWTError): jwt.get_unverified_headers(bad_token) def test_non_default_headers(self, claims, key, headers): encoded = jwt.encode(claims, key, headers=headers) decoded = jwt.decode(encoded, key) assert claims == decoded all_headers = jwt.get_unverified_headers(encoded) for k, v in headers.items(): assert all_headers[k] == v def test_deterministic_headers(self): from collections import OrderedDict + from jose.utils import base64url_decode claims = {"a": "b"} key = "secret" - headers1 = OrderedDict(( - ('kid', 'my-key-id'), - ('another_key', 'another_value'), - )) - encoded1 = jwt.encode(claims, key, algorithm='HS256', headers=headers1) - encoded_headers1 = encoded1.split('.', 1)[0] + headers1 = OrderedDict( + ( + ("kid", "my-key-id"), + ("another_key", "another_value"), + ) + ) + encoded1 = jwt.encode(claims, key, algorithm="HS256", headers=headers1) + encoded_headers1 = encoded1.split(".", 1)[0] - headers2 = OrderedDict(( - ('another_key', 'another_value'), - ('kid', 'my-key-id'), - )) - encoded2 = jwt.encode(claims, key, algorithm='HS256', headers=headers2) - encoded_headers2 = encoded2.split('.', 1)[0] + headers2 = OrderedDict( + ( + ("another_key", "another_value"), + ("kid", "my-key-id"), + ) + ) + encoded2 = jwt.encode(claims, key, algorithm="HS256", headers=headers2) + encoded_headers2 = encoded2.split(".", 1)[0] assert encoded_headers1 == encoded_headers2 # manually decode header to compare it to known good - decoded_headers1 = base64url_decode(encoded_headers1.encode('utf-8')) + decoded_headers1 = base64url_decode(encoded_headers1.encode("utf-8")) assert decoded_headers1 == b"""{"alg":"HS256","another_key":"another_value","kid":"my-key-id","typ":"JWT"}""" def test_encode(self, claims, key): expected = ( - ( - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9' - '.eyJhIjoiYiJ9' - '.xNtk2S0CNbCBZX_f67pFgGRugaP1xi2ICfet3nwOSxw' - ), - ( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' - '.eyJhIjoiYiJ9' - '.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' - ) + ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9" ".eyJhIjoiYiJ9" ".xNtk2S0CNbCBZX_f67pFgGRugaP1xi2ICfet3nwOSxw"), + ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJhIjoiYiJ9" ".jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8"), ) encoded = jwt.encode(claims, key) assert encoded in expected def test_decode(self, claims, key): - token = ( - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' - '.eyJhIjoiYiJ9' - '.jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8' - ) + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJhIjoiYiJ9" ".jiMyrsmD8AoHWeQgmxZ5yq8z0lXS67_QGs52AzC8Ru8" decoded = jwt.decode(token, key) assert decoded == claims + @pytest.mark.parametrize( + "key", + [ + b"key", + "key", + ], + ) + def test_round_trip_with_different_key_types(self, key): + token = jwt.encode({"testkey": "testvalue"}, key, algorithm="HS256") + verified_data = jwt.decode(token, key, algorithms=["HS256"]) + assert "testkey" in verified_data.keys() + assert verified_data["testkey"] == "testvalue" + def test_leeway_is_int(self): pass def test_leeway_is_timedelta(self, claims, key): nbf = datetime.utcnow() + timedelta(seconds=5) leeway = timedelta(seconds=10) claims = { - 'nbf': nbf, + "nbf": nbf, } - options = { - 'leeway': leeway - } + options = {"leeway": leeway} token = jwt.encode(claims, key) jwt.decode(token, key, options=options) def test_iat_not_int(self, key): - claims = { - 'iat': 'test' - } + claims = {"iat": "test"} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_nbf_not_int(self, key): - claims = { - 'nbf': 'test' - } + claims = {"nbf": "test"} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_nbf_datetime(self, key): nbf = datetime.utcnow() - timedelta(seconds=5) - claims = { - 'nbf': nbf - } + claims = {"nbf": nbf} token = jwt.encode(claims, key) jwt.decode(token, key) def test_nbf_with_leeway(self, key): nbf = datetime.utcnow() + timedelta(seconds=5) claims = { - 'nbf': nbf, + "nbf": nbf, } - options = { - 'leeway': 10 - } + options = {"leeway": 10} token = jwt.encode(claims, key) jwt.decode(token, key, options=options) def test_nbf_in_future(self, key): nbf = datetime.utcnow() + timedelta(seconds=5) - claims = { - 'nbf': nbf - } + claims = {"nbf": nbf} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_nbf_skip(self, key): nbf = datetime.utcnow() + timedelta(seconds=5) - claims = { - 'nbf': nbf - } + claims = {"nbf": nbf} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) - options = { - 'verify_nbf': False - } + options = {"verify_nbf": False} jwt.decode(token, key, options=options) def test_exp_not_int(self, key): - claims = { - 'exp': 'test' - } + claims = {"exp": "test"} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_exp_datetime(self, key): exp = datetime.utcnow() + timedelta(seconds=5) - claims = { - 'exp': exp - } + claims = {"exp": exp} token = jwt.encode(claims, key) jwt.decode(token, key) def test_exp_with_leeway(self, key): exp = datetime.utcnow() - timedelta(seconds=5) claims = { - 'exp': exp, + "exp": exp, } - options = { - 'leeway': 10 - } + options = {"leeway": 10} token = jwt.encode(claims, key) jwt.decode(token, key, options=options) def test_exp_in_past(self, key): exp = datetime.utcnow() - timedelta(seconds=5) - claims = { - 'exp': exp - } + claims = {"exp": exp} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_exp_skip(self, key): exp = datetime.utcnow() - timedelta(seconds=5) - claims = { - 'exp': exp - } + claims = {"exp": exp} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) - options = { - 'verify_exp': False - } + options = {"verify_exp": False} jwt.decode(token, key, options=options) def test_aud_string(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': aud - } + claims = {"aud": aud} token = jwt.encode(claims, key) jwt.decode(token, key, audience=aud) def test_aud_list(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': [aud] - } + claims = {"aud": [aud]} token = jwt.encode(claims, key) jwt.decode(token, key, audience=aud) def test_aud_list_multiple(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': [aud, 'another'] - } + claims = {"aud": [aud, "another"]} token = jwt.encode(claims, key) jwt.decode(token, key, audience=aud) def test_aud_list_is_strings(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': [aud, 1] - } + claims = {"aud": [aud, 1]} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key, audience=aud) def test_aud_case_sensitive(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': [aud] - } + claims = {"aud": [aud]} token = jwt.encode(claims, key) with pytest.raises(JWTError): - jwt.decode(token, key, audience='AUDIENCE') + jwt.decode(token, key, audience="AUDIENCE") def test_aud_empty_claim(self, claims, key): - aud = 'audience' + aud = "audience" token = jwt.encode(claims, key) jwt.decode(token, key, audience=aud) def test_aud_not_string_or_list(self, key): aud = 1 - claims = { - 'aud': aud - } + claims = {"aud": aud} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_aud_given_number(self, key): - aud = 'audience' + aud = "audience" - claims = { - 'aud': aud - } + claims = {"aud": aud} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key, audience=1) def test_iss_string(self, key): - iss = 'issuer' + iss = "issuer" - claims = { - 'iss': iss - } + claims = {"iss": iss} token = jwt.encode(claims, key) jwt.decode(token, key, issuer=iss) def test_iss_list(self, key): - iss = 'issuer' + iss = "issuer" - claims = { - 'iss': iss - } + claims = {"iss": iss} token = jwt.encode(claims, key) - jwt.decode(token, key, issuer=['https://issuer', 'issuer']) + jwt.decode(token, key, issuer=["https://issuer", "issuer"]) def test_iss_tuple(self, key): - iss = 'issuer' + iss = "issuer" - claims = { - 'iss': iss - } + claims = {"iss": iss} token = jwt.encode(claims, key) - jwt.decode(token, key, issuer=('https://issuer', 'issuer')) + jwt.decode(token, key, issuer=("https://issuer", "issuer")) def test_iss_invalid(self, key): - iss = 'issuer' + iss = "issuer" - claims = { - 'iss': iss - } + claims = {"iss": iss} token = jwt.encode(claims, key) with pytest.raises(JWTError): - jwt.decode(token, key, issuer='another') + jwt.decode(token, key, issuer="another") def test_sub_string(self, key): - sub = 'subject' + sub = "subject" - claims = { - 'sub': sub - } + claims = {"sub": sub} token = jwt.encode(claims, key) jwt.decode(token, key) def test_sub_invalid(self, key): sub = 1 - claims = { - 'sub': sub - } + claims = {"sub": sub} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_sub_correct(self, key): - sub = 'subject' + sub = "subject" - claims = { - 'sub': sub - } + claims = {"sub": sub} token = jwt.encode(claims, key) jwt.decode(token, key, subject=sub) def test_sub_incorrect(self, key): - sub = 'subject' + sub = "subject" - claims = { - 'sub': sub - } + claims = {"sub": sub} token = jwt.encode(claims, key) with pytest.raises(JWTError): - jwt.decode(token, key, subject='another') + jwt.decode(token, key, subject="another") def test_jti_string(self, key): - jti = 'JWT ID' + jti = "JWT ID" - claims = { - 'jti': jti - } + claims = {"jti": jti} token = jwt.encode(claims, key) jwt.decode(token, key) def test_jti_invalid(self, key): jti = 1 - claims = { - 'jti': jti - } + claims = {"jti": jti} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key) def test_at_hash(self, claims, key): - access_token = '' + access_token = "" token = jwt.encode(claims, key, access_token=access_token) payload = jwt.decode(token, key, access_token=access_token) - assert 'at_hash' in payload + assert "at_hash" in payload def test_at_hash_invalid(self, claims, key): - token = jwt.encode(claims, key, access_token='') + token = jwt.encode(claims, key, access_token="") with pytest.raises(JWTError): - jwt.decode(token, key, access_token='') + jwt.decode(token, key, access_token="") def test_at_hash_missing_access_token(self, claims, key): - token = jwt.encode(claims, key, access_token='') + token = jwt.encode(claims, key, access_token="") with pytest.raises(JWTError): jwt.decode(token, key) def test_at_hash_missing_claim(self, claims, key): token = jwt.encode(claims, key) - payload = jwt.decode(token, key, access_token='') - assert 'at_hash' not in payload + payload = jwt.decode(token, key, access_token="") + assert "at_hash" not in payload def test_at_hash_unable_to_calculate(self, claims, key): - token = jwt.encode(claims, key, access_token='') + token = jwt.encode(claims, key, access_token="") with pytest.raises(JWTError): - jwt.decode(token, key, access_token='\xe2') + jwt.decode(token, key, access_token="\xe2") def test_bad_claims(self): - bad_token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.iOJ5SiNfaNO_pa2J4Umtb3b3zmk5C18-mhTCVNsjnck' + bad_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.iOJ5SiNfaNO_pa2J4Umtb3b3zmk5C18-mhTCVNsjnck" with pytest.raises(JWTError): jwt.get_unverified_claims(bad_token) def test_unverified_claims_string(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.aW52YWxpZCBjbGFpbQ.iOJ5SiNfaNO_pa2J4Umtb3b3zmk5C18-mhTCVNsjnck' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.aW52YWxpZCBjbGFpbQ.iOJ5SiNfaNO_pa2J4Umtb3b3zmk5C18-mhTCVNsjnck" with pytest.raises(JWTError): jwt.get_unverified_claims(token) def test_unverified_claims_list(self): - token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.WyJpbnZhbGlkIiwgImNsYWltcyJd.nZvw_Rt1FfUPb5OiVbrSYZGtWSE5c-gdJ6nQnTTBkYo' + token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.WyJpbnZhbGlkIiwgImNsYWltcyJd.nZvw_Rt1FfUPb5OiVbrSYZGtWSE5c-gdJ6nQnTTBkYo" with pytest.raises(JWTError): jwt.get_unverified_claims(token) def test_unverified_claims_object(self, claims, key): token = jwt.encode(claims, key) assert jwt.get_unverified_claims(token) == claims @pytest.mark.parametrize( - "claim,value", [ + "claim,value", + [ ("aud", "aud"), ("ait", "ait"), ("exp", datetime.utcnow() + timedelta(seconds=3600)), ("nbf", datetime.utcnow() - timedelta(seconds=5)), ("iss", "iss"), ("sub", "sub"), ("jti", "jti"), - ] + ], ) def test_require(self, claims, key, claim, value): options = {"require_" + claim: True, "verify_" + claim: False} token = jwt.encode(claims, key) with pytest.raises(JWTError): jwt.decode(token, key, options=options, audience=str(value)) new_claims = dict(claims) new_claims[claim] = value token = jwt.encode(new_claims, key) jwt.decode(token, key, options=options, audience=str(value)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 71ced1f..2fbb08d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,16 +1,14 @@ - from datetime import timedelta from jose import utils class TestUtils: - def test_total_seconds(self): td = timedelta(seconds=5) assert utils.timedelta_total_seconds(td) == 5 def test_long_to_base64(self): - assert utils.long_to_base64(0xDEADBEEF) == b'3q2-7w' - assert utils.long_to_base64(0xCAFED00D, size=10) == b'AAAAAAAAyv7QDQ' + assert utils.long_to_base64(0xDEADBEEF) == b"3q2-7w" + assert utils.long_to_base64(0xCAFED00D, size=10) == b"AAAAAAAAyv7QDQ" diff --git a/tox.ini b/tox.ini index f709556..b757214 100644 --- a/tox.ini +++ b/tox.ini @@ -1,50 +1,79 @@ [tox] minversion = 3.4.0 envlist = - py{27,35,36,py}-{base,cryptography-only,pycryptodome-norsa,pycrypto-norsa,compatibility}, - flake8 + py{36,37,38,39,py3}-{base,cryptography-only,pycryptodome-norsa,compatibility}, + lint skip_missing_interpreters = True +[gh-actions] +python = + 3.6: py36-{base,cryptography-only,pycryptodome-norsa,compatibility} + 3.7: py37-{base,cryptography-only,pycryptodome-norsa,compatibility} + 3.8: py38-{base,cryptography-only,pycryptodome-norsa,compatibility} + 3.9: py39-{base,cryptography-only,pycryptodome-norsa,compatibility} + pypy3: pypy3-{base,cryptography-only,pycryptodome-norsa,compatibility} + [testenv:basecommand] commands = pip --version - py.test --cov-report term-missing --cov jose {posargs} + pytest --cov-append --cov-report term-missing --cov jose {posargs} + + +[testenv:pypy3-compatibility] +# This testenv locks up during coverage so just run tests +commands = + pip --version + pytest [testenv:compatibility] extras = cryptography - pycrypto pycryptodome [testenv] deps = pytest pytest-cov pytest-runner + commands_pre = # Remove the python-rsa and python-ecdsa backends only: pip uninstall -y ecdsa rsa # Remove just the python-rsa backend norsa: pip uninstall -y rsa commands = # Test the python-rsa backend - base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or pycrypto or backend_compatibility)" + base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or backend_compatibility)" # Test the pyca/cryptography backend - cryptography: {[testenv:basecommand]commands} -m "not (pycryptodome or pycrypto or backend_compatibility)" + cryptography: {[testenv:basecommand]commands} -m "not (pycryptodome or backend_compatibility)" # Test the pycryptodome backend - pycryptodome: {[testenv:basecommand]commands} -m "not (cryptography or pycrypto or backend_compatibility)" - # Test the pycrypto backend - pycrypto: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or backend_compatibility)" + pycryptodome: {[testenv:basecommand]commands} -m "not (cryptography or backend_compatibility)" # Test cross-backend compatibility and coexistence compatibility: {[testenv:basecommand]commands} extras = cryptography: cryptography pycryptodome: pycryptodome - pycrypto: pycrypto compatibility: {[testenv:compatibility]extras} -[testenv:flake8] +[testenv:lint] +basepython = python3.8 skip_install= True deps = flake8 -commands = flake8 jose setup.py + isort + black +commands = + flake8 jose setup.py + isort jose tests setup.py --check-only + black . --check + + +[testenv:lintfix] +basepython = python3.8 +skip_install= True +deps = + isort + black +commands = + isort jose tests setup.py + black .