pax_global_header00006660000000000000000000000064150142451060014510gustar00rootroot0000000000000052 comment=68df43f5bcff86dffdfdbbdf723a35152f5583ba joserfc-1.1.0/000077500000000000000000000000001501424510600131425ustar00rootroot00000000000000joserfc-1.1.0/.devcontainer/000077500000000000000000000000001501424510600157015ustar00rootroot00000000000000joserfc-1.1.0/.devcontainer/devcontainer.json000066400000000000000000000005101501424510600212510ustar00rootroot00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { "name": "Python 3", "image": "mcr.microsoft.com/devcontainers/python:1-3.8-bookworm", "postCreateCommand": "pip3 install --user -r requirements-dev.lock" } joserfc-1.1.0/.editorconfig000066400000000000000000000003231501424510600156150ustar00rootroot00000000000000root = true [*] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf charset = utf-8 max_line_length = 120 [*.{yml,yaml,json,toml}] indent_size = 2 joserfc-1.1.0/.github/000077500000000000000000000000001501424510600145025ustar00rootroot00000000000000joserfc-1.1.0/.github/CODE_OF_CONDUCT.md000066400000000000000000000026171501424510600173070ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) joserfc-1.1.0/.github/FUNDING.yml000066400000000000000000000000401501424510600163110ustar00rootroot00000000000000github: - authlib - lepture joserfc-1.1.0/.github/workflows/000077500000000000000000000000001501424510600165375ustar00rootroot00000000000000joserfc-1.1.0/.github/workflows/docs.yml000066400000000000000000000022001501424510600202040ustar00rootroot00000000000000name: Publish docs # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: contents: read pages: write id-token: write # Allow one concurrent deployment concurrency: group: "pages" cancel-in-progress: true on: push: branches: - main paths-ignore: - ".github/*" - "tests/*" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - name: install dependencies run: | pip install -r requirements-dev.lock pip install -r requirements-docs.lock - name: sphinx build run: | make build-docs -e lang=en make build-docs -e lang=zh - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 joserfc-1.1.0/.github/workflows/pypi.yml000066400000000000000000000024411501424510600202440ustar00rootroot00000000000000name: Release to PyPI permissions: contents: write on: push: tags: - "1.*" jobs: build: name: build dist files runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: 3.9 - name: install build run: python -m pip install --upgrade build - name: build dist run: python -m build - uses: actions/upload-artifact@v4 with: name: artifacts path: dist/* if-no-files-found: error publish: environment: name: pypi-release url: https://pypi.org/project/joserfc/ permissions: id-token: write name: release to pypi needs: build runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: name: artifacts path: dist - name: Push build artifacts to PyPI uses: pypa/gh-action-pypi-publish@release/v1 release: name: write release note runs-on: ubuntu-latest needs: publish steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 - run: npx changelogithub --no-group continue-on-error: true env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} joserfc-1.1.0/.github/workflows/test.yml000066400000000000000000000032061501424510600202420ustar00rootroot00000000000000name: Test permissions: contents: read on: push: branches-ignore: - 'wip-*' paths-ignore: - '.github/**' - 'docs/**' - '*.md' - '*.rst' pull_request: branches-ignore: - 'wip-*' paths-ignore: - '.github/**' - 'docs/**' - '*.md' - '*.rst' jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Install dependencies run: | pip install -r requirements-dev.lock - name: ruff lint run: ruff check - name: mypy lint run: mypy test: needs: lint runs-on: ubuntu-latest strategy: fail-fast: false max-parallel: 3 matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | pip install -r requirements-dev.lock - name: Report coverage run: pytest --cov=joserfc --cov-report=xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml flags: unittests name: GitHub - name: SonarCloud Scan uses: SonarSource/sonarqube-scan-action@v4 continue-on-error: true env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} joserfc-1.1.0/.gitignore000066400000000000000000000012021501424510600151250ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .venv/ .coverage .coverage.* .cache coverage.xml *.cover *.py,cover .pytest_cache/ .mypy_cache/ # Sphinx documentation docs/_build/ # pyenv .python-version __pypackages__/ # IDE .idea/ .vscode/ *.mo uv.lock public/en public/zh public/sitemap.xml demo.py joserfc-1.1.0/.pre-commit-config.yaml000066400000000000000000000006121501424510600174220ustar00rootroot00000000000000--- repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.11.6' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell additional_dependencies: - tomli exclude: "docs/locales" args: [--write-changes] joserfc-1.1.0/LICENSE000066400000000000000000000027351501424510600141560ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2023, Hsiaoming Yang Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. joserfc-1.1.0/MANIFEST.in000066400000000000000000000002161501424510600146770ustar00rootroot00000000000000include LICENSE include README.rst recursive-include tests * # include documentation sources but not built docs graft docs prune docs/_build joserfc-1.1.0/Makefile000066400000000000000000000004721501424510600146050ustar00rootroot00000000000000lang=en dev-docs: sphinx-build docs public/${lang} -D language=${lang} -b dirhtml -a build-docs: @sphinx-build docs public/${lang} -D language=${lang} -b dirhtml @rm public/${lang}/.buildinfo @rm -r public/${lang}/.doctrees clean-docs: @rm -fr public/${lang} coverage: @pytest --cov --cov-report=html joserfc-1.1.0/README.md000066400000000000000000000057071501424510600144320ustar00rootroot00000000000000
Authlib JOSE RFC `joserfc` is a Python library that provides a comprehensive implementation of several essential JSON Object Signing and Encryption (JOSE) standards. [![Build Status](https://github.com/authlib/joserfc/actions/workflows/test.yml/badge.svg)](https://github.com/authlib/joserfc/actions) [![PyPI Version](https://badgen.net/pypi/v/joserfc)](https://pypi.org/project/joserfc) [![PyPI Downloads](https://badgen.net/pypi/dm/joserfc)](https://pepy.tech/projects/joserfc) [![Code Coverage](https://codecov.io/gh/authlib/joserfc/branch/main/graph/badge.svg?token=WCD9X8HKI1)](https://codecov.io/gh/authlib/joserfc) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=authlib_joserfc&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=authlib_joserfc) [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=authlib_joserfc&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=authlib_joserfc)
## Usage A quick and simple JWT encoding and decoding would look something like this: ```python from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("secret") encoded = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) # 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrIjoidmFsdWUifQ.ni-MJXnZHpFB_8L9P9yllj3RNDfzmD4yBKAyefSctMY' token = jwt.decode(encoded, key) print(token.header) # {'alg': 'HS256', 'typ': 'JWT'} print(token.claims) # {'k': 'value'} # validate claims (if needed) claims_requests = jwt.JWTClaimsRegistry() claims_requests.validate(token.claims) ``` ## Features It follows RFCs with extensible API. The module has implementations of: - RFC7515: [JSON Web Signature](https://jose.authlib.org/en/dev/guide/jws/) - RFC7516: [JSON Web Encryption](https://jose.authlib.org/en/dev/guide/jwe/) - RFC7517: [JSON Web Key](https://jose.authlib.org/en/dev/guide/jwk/) - RFC7518: [JSON Web Algorithms](https://jose.authlib.org/en/dev/guide/algorithms/) - RFC7519: [JSON Web Token](https://jose.authlib.org/en/dev/guide/jwt/) - RFC7520: Examples of Protecting Content Using JSON Object Signing and Encryption - RFC7638: ``thumbprint`` for JWK - RFC7797: [JSON Web Signature (JWS) Unencoded Payload Option](https://jose.authlib.org/en/dev/guide/jws/#rfc7797) - RFC8037: ``OKP`` Key and ``EdDSA`` algorithm - RFC8812: ``ES256K`` algorithm And draft RFCs implementation of: - [`C20P` and `XC20P`](https://jose.authlib.org/en/dev/guide/algorithms/#c20p-and-xc20p) - [Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model](https://jose.authlib.org/en/dev/guide/algorithms/#ecdh-1pu-algorithms) ## Useful Links - Documentation: https://jose.authlib.org/ - Blog: https://blog.authlib.org/. - Twitter: https://twitter.com/authlib. ## License 2023, Hsiaoming Yang. Under BSD-3 license. joserfc-1.1.0/README.rst000066400000000000000000000026611501424510600146360ustar00rootroot00000000000000JOSE RFC ======== `·joserfc·` is a Python library that provides a comprehensive implementation of several essential JSON Object Signing and Encryption (JOSE) standards. This package contains implementation of: - RFC7515: JSON Web Signature - RFC7516: JSON Web Encryption - RFC7517: JSON Web Key - RFC7518: JSON Web Algorithms - RFC7519: JSON Web Token - RFC7520: Examples of Protecting Content Using JSON Object Signing and Encryption - RFC7638: thumbprint for JWK - RFC8037: OKP Key and EdDSA algorithm - RFC8812: ES256K algorithm And draft RFCs implementation of: - C20P and XC20P - ECDH-1PU algorithms Usage ----- A quick and simple JWT encoding and decoding would look something like this: .. code-block:: python >>> from joserfc import jwt >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> encoded = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) >>> encoded 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrIjoidmFsdWUifQ.ni-MJXnZHpFB_8L9P9yllj3RNDfzmD4yBKAyefSctMY' >>> token = jwt.decode(encoded, key) >>> token.header {'alg': 'HS256', 'typ': 'JWT'} >>> token.claims {'k': 'value'} >>> claims_requests = jwt.JWTClaimsRegistry() >>> claims_requests.validate(token.claims) Useful Links ------------ 1. GitHub: https://github.com/authlib/joserfc 2. Docs: https://jose.authlib.org/en/ License ------- Licensed under BSD. Please see LICENSE for licensing details. joserfc-1.1.0/docs/000077500000000000000000000000001501424510600140725ustar00rootroot00000000000000joserfc-1.1.0/docs/Makefile000066400000000000000000000001601501424510600155270ustar00rootroot00000000000000gettext: sphinx-build -b gettext . _build/gettext update-locales: sphinx-intl update -p _build/gettext -l zh joserfc-1.1.0/docs/_static/000077500000000000000000000000001501424510600155205ustar00rootroot00000000000000joserfc-1.1.0/docs/_static/custom.css000066400000000000000000000003371501424510600175470ustar00rootroot00000000000000:root { --syntax-light-pre-bg: #ecf5ff; --syntax-light-cap-bg: #d6e7fb; --syntax-light-highlight-bg: #dbecff; --syntax-dark-pre-bg: #1a2b3e; --syntax-dark-cap-bg: #223e5e; --syntax-dark-highlight-bg: #091c33; } joserfc-1.1.0/docs/_static/dark-logo.svg000066400000000000000000000424651501424510600201330ustar00rootroot00000000000000 joserfc-1.1.0/docs/_static/icon.svg000077500000000000000000000012421501424510600171730ustar00rootroot00000000000000joserfc-1.1.0/docs/_static/light-logo.svg000066400000000000000000000424671501424510600203230ustar00rootroot00000000000000 joserfc-1.1.0/docs/api/000077500000000000000000000000001501424510600146435ustar00rootroot00000000000000joserfc-1.1.0/docs/api/errors.rst000066400000000000000000000001631501424510600167110ustar00rootroot00000000000000Errors ====== All errors are based on ``joserfc.errors.JoseError``. .. automodule:: joserfc.errors :members: joserfc-1.1.0/docs/api/index.rst000066400000000000000000000015341501424510600165070ustar00rootroot00000000000000API References ============== Here covers the interfaces of JWS, JWE, JWK, and JWT. .. grid:: 2 :gutter: 2 :padding: 0 .. grid-item-card:: JWS API :link-type: ref :link: jws_api Most :ref:`jwt` are encoded with JWS in compact serialization. .. grid-item-card:: JWE API :link-type: ref :link: jwe_api JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures. .. grid-item-card:: JWK API :link-type: ref :link: jwk_api Learn how to use ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and JSON Web Key Set. .. grid-item-card:: JWT API :link-type: ref :link: jwt_api JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`. .. toctree:: :hidden: jws jwe jwk jwt errors joserfc-1.1.0/docs/api/jwe.rst000066400000000000000000000002271501424510600161630ustar00rootroot00000000000000.. _jwe_api: JWE API ======= This part of the documentation covers all the interfaces of ``joserfc.jwe``. .. automodule:: joserfc.jwe :members: joserfc-1.1.0/docs/api/jwk.rst000066400000000000000000000002271501424510600161710ustar00rootroot00000000000000.. _jwk_api: JWK API ======= This part of the documentation covers all the interfaces of ``joserfc.jwk``. .. automodule:: joserfc.jwk :members: joserfc-1.1.0/docs/api/jws.rst000066400000000000000000000002271501424510600162010ustar00rootroot00000000000000.. _jws_api: JWS API ======= This part of the documentation covers all the interfaces of ``joserfc.jws``. .. automodule:: joserfc.jws :members: joserfc-1.1.0/docs/api/jwt.rst000066400000000000000000000002271501424510600162020ustar00rootroot00000000000000.. _jwt_api: JWT API ======= This part of the documentation covers all the interfaces of ``joserfc.jwt``. .. automodule:: joserfc.jwt :members: joserfc-1.1.0/docs/changelog.rst000066400000000000000000000105421501424510600165550ustar00rootroot00000000000000Changelog ========= .. rst-class:: lead Here is the history of joserfc_ package releases. .. _joserfc: https://pypi.org/project/joserfc/ ---- .. module:: joserfc :noindex: 1.1.0 ----- **Released on May 24, 2025** - Use "import as" to prioritize the modules for editors. - Added parameter ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` for ``jwt.decode``. - Added ``none`` algorithm for JWS. - Added ``jwk.import_key`` and ``jwk.generate_key`` aliases. **Breaking changes**: - Use ``ECKey.binding.register_curve`` to register new supported curves. - Use ``UnsupportedAlgorithmError`` instead of ``ValueError`` in JWS/JWE registry. - Use ``MissingKeyTypeError`` and ``InvalidKeyIdError`` for errors in JWK. - Use ``UnsupportedHeaderError``, ``MissingHeaderError``, and ``MissingCritHeaderError`` for header validation. - Respect RFC6749 character set in error descriptions. 1.0.4 ----- **Released on Feb 28, 2025** - Use secrets module to generate random bytes. - Use warnings for possible unsafe ``OctKey``` instead of raising error, via :issue:`32`. 1.0.3 ----- **Released on Feb 6, 2025** - Allow using sha256, sha384, sha512 hash functions in thumbprint (RFC7638). 1.0.2 ----- **Released on Jan 20, 2025** - Support import key from a certificate pem file. 1.0.1 ----- **Released on December 3, 2024** - Throw an error on non-valid base64 strings. 1.0.0 ----- **Released on July 14, 2024** - Fix type hints for strict mode. 0.12.0 ------ **Released on June 15, 2024** - Limit DEF decompress size to 250k bytes. - Fix claims validation, via :issue:`23`. 0.11.1 ------ **Released on June 4, 2024** - Remove validating ``typ`` header with ``jwt.decode`` method. 0.11.0 ------ **Released on June 4, 2024** - ``jwe.decrypt_json`` allows to verify only one recipient. - Prevent ``OctKey`` to import ``ssh-dss``. - Deprecate use of string and bytes as key. 0.10.0 ------ **Released on May 13, 2024** - Change ``jwt.encode`` and ``jwt.decode`` to use JWS by default. 0.9.0 ----- **Released on November 16, 2023** - Use ``os.urandom`` for ``OctKey.generate_key``. - Add ``allow_blank`` for ``JWTClaimsRegistry``. - Improve callable key for :meth:`~jwk.guess_key`. 0.8.0 ----- **Released on September 06, 2023** - Add :ref:`ensure_kid` method on key models. - Add ``auto_kid`` parameter on key model ``.generate_key`` method. - Improvements on type hints 0.7.0 ----- **Released on August 14, 2023** - Add "iat" claims validation in JWT. - Add ``__bool__`` magic method on :class:`jwk.KeySet`. - Raise ``InvalidExchangeKeyError`` for ``exchange_derive_key`` on Curve key. - Improvements on type hints 0.6.0 ----- **Released on July 20, 2023** - Huge improvements on type hints, via :user:`Viicos`. - Do not mutate the header when ``jwt.encode``, via :issue:`6`. - Register algorithms with their matched key types on key set. - Improve error handling, raise proper errors. **Breaking changes**: - ``jws.JSONSignature`` is replaced by :class:`jws.GeneralJSONSignature` and :class:`jws.FlattenedJSONSignature`. - ``jwe.JSONEncryption`` is replaced by :class:`jwe.GeneralJSONEncryption` and :class:`jwe.FlattenedJSONEncryption`. 0.5.0 ----- **Released on July 12, 2023** - Add RFC7797 JSON Web Signature (JWS) Unencoded Payload Option - Fix ``decrypt_json`` when there is no ``encrypted_key`` - Rename JWE CompleteJSONSerialization to GeneralJSONSerialization - Rename ``JSONEncryption.flatten`` to ``.flattened`` - Load and dump RSA, EC, and OKP key with password - Rename Curve key method: ``exchange_shared_key`` to ``exchange_derive_key`` 0.4.0 ----- **Released on July 6, 2023** - Change ``options`` to ``parameters`` for JWK methods - Change ``JWSRegistry`` and ``JWERegistry`` parameters - Guess ``sender_key`` from JWKs in JWE - Add importing key from DER encoding bytes - Fix JWS JSON serialization when members have only unprotected headers - Check key type before processing algorithms of JWS and JWE 0.3.0 ----- **Released on June 29, 2023** - Return ``str`` instead of ``bytes`` for JWS and JWE serializations - Add a ``detach_content`` method for JWS - Remove ``jwt.extract`` method, because ``extract`` won't work for JWE - Add ``JWKRegistry`` for JWK - Update ``JSONEncryption.add_recipient`` parameters - Export register methods for JWE drafts 0.2.0 ----- **Released on June 25, 2023** A beta release. 0.1.0 ----- **Released on March 5, 2023** Initial release. joserfc-1.1.0/docs/conf.py000066400000000000000000000052751501424510600154020ustar00rootroot00000000000000from joserfc import __version__ project = "joserfc" copyright = "Copyright © 2023, Hsiaoming Yang" author = "Hsiaoming Yang" version = __version__ release = __version__ language = "en" locale_dirs = ["locales/"] html_title = "joserfc" html_static_path = ["_static"] html_css_files = [ "custom.css", ] html_theme = "shibuya" html_copy_source = False html_show_sourcelink = False extensions = [ "sphinx.ext.autodoc", "sphinx.ext.extlinks", "sphinx_copybutton", "sphinx_design", "sphinx_sitemap", "sphinx_contributors", ] extlinks = { "user": ("https://github.com/%s", "@%s"), "pull": ("https://github.com/authlib/joserfc/pull/%s", "pull request #%s"), "issue": ("https://github.com/authlib/joserfc/issues/%s", "issue #%s"), } intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } html_favicon = "_static/icon.svg" html_theme_options = { "accent_color": "blue", "light_logo": "_static/light-logo.svg", "dark_logo": "_static/dark-logo.svg", "twitter_site": "authlib", "twitter_creator": "lepture", "twitter_url": "https://twitter.com/authlib", "github_url": "https://github.com/authlib/joserfc", "discord_url": "https://discord.gg/HvBVAeNAaV", "carbon_ads_code": "CE7DKK3W", "carbon_ads_placement": "joseauthliborg", "nav_links": [ { "title": "Projects", "children": [ {"title": "Authlib", "url": "https://authlib.org/", "summary": "OAuth, JOSE, OpenID, etc."}, {"title": "JOSE RFC", "url": "https://jose.authlib.org/", "summary": "JWS, JWE, JWK, and JWT."}, { "title": "OTP Auth", "url": "https://otp.authlib.org/", "summary": "One time password, HOTP/TOTP.", }, ], }, {"title": "Sponsor me", "url": "https://github.com/sponsors/authlib"}, ], } html_baseurl = "https://jose.authlib.org/en/" html_context = { "source_type": "github", "source_user": "authlib", "source_repo": "joserfc", "source_docs_path": "/docs/", } # sitemap configuration site_url = "https://jose.authlib.org/" sitemap_url_scheme = "{lang}{link}" sitemap_filename = "../sitemap.xml" sitemap_locales = [] def setup(app): global language, html_baseurl, sitemap_filename, sitemap_locales language = app.config.language if language != "en": sitemap_filename = "sitemap.xml" sitemap_locales = [None] html_baseurl = f"https://jose.authlib.org/{language}/" html_context["languages"] = [ ("English", "https://jose.authlib.org/en/%s/", "en"), ("简体中文", "https://jose.authlib.org/zh/%s/", "zh"), ] joserfc-1.1.0/docs/contributing/000077500000000000000000000000001501424510600166015ustar00rootroot00000000000000joserfc-1.1.0/docs/contributing/authors.rst000066400000000000000000000004461501424510600210240ustar00rootroot00000000000000Authors ======= ``joserfc`` is written and maintained by `Hsiaoming Yang `_. Contributors ------------ Here is the list of the main contributors: .. contributors:: authlib/joserfc :contributions: And more on https://github.com/authlib/joserfc/graphs/contributors joserfc-1.1.0/docs/contributing/index.rst000066400000000000000000000031051501424510600204410ustar00rootroot00000000000000Contributing ============ Contributions are welcome, and they are greatly appreciated! Types of contributions ---------------------- There are many ways you can contribute. Report bugs ~~~~~~~~~~~ You're welcome to report bugs at `GitHub Issues `_. Before reporting a bug, please verify your bug against the latest code in ``main`` branch. When reporting a bug, please including: - Your operating system name and version. - Your Python version. - Details to reproduce the bug. Submit fixes ~~~~~~~~~~~~ Once you found a bug that you can fix, you're welcome to submit your pull request. Please follow our `git commit conventions `_. Improve documentation ~~~~~~~~~~~~~~~~~~~~~ Everyone wants a good documentation. There may be mistakes or things missing in the documentation, you're welcome to help us improving the documentation. .. _development: Development ----------- Once you cloned ``joserfc``'s source code, you can setup a development environment to work on. venv ~~~~ I strongly suggest you create a virtual environment with ``venv``: .. code-block:: shell python -m venv .venv source .venv/bin/active Install ~~~~~~~ Then install the Python requirements for development: .. code-block:: shell pip install -r requirements.txt Run tests ~~~~~~~~~~ Once you made some code changes, you can add your test case in the ``tests`` folder, then verify it with: .. code-block:: shell pytest Next ---- .. toctree:: structure translation authors sponsors joserfc-1.1.0/docs/contributing/sponsors.rst000066400000000000000000000000221501424510600212130ustar00rootroot00000000000000Sponsors ======== joserfc-1.1.0/docs/contributing/structure.rst000066400000000000000000000056621501424510600214040ustar00rootroot00000000000000Code structure ============== The code structure of ``joserfc`` follows an organized approach based on RFC specifications. It is designed to enhance understanding by grouping the code according to specific RFCs. Overview -------- The overall structure is organized as follows: .. code-block:: none joserfc/ rfc7515/ # Code related to RFC7515 (JWS) rfc7516/ # Code related to RFC7516 (JWE) rfc7517/ # Code related to RFC7517 (JWK) rfc7518/ # Code related to RFC7518 (JWA) rfc7519/ # Code related to RFC7519 (JWT) rfc7638/ # Code related to RFC7638 (JWK Thumbprint) rfc8037/ # Code related to RFC8037 (OKP Keys) rfc8812/ # Code related to RFC8812 (secp256k1 Curve) jws.py # High-level API for JWS operations jwe.py # High-level API for JWE operations jwk.py # High-level API for JWK operations jwt.py # High-level API for JWT operations This structure allows developers to easily navigate and comprehend each RFC specification individually. The code is organized from low-level to high-level, making it intuitive and convenient to understand and use. Developers can utilize the higher-level APIs (``jws.py``, ``jwe.py``, ``jwk.py``, ``jwt.py``) without needing to delve into the lower-level implementation details. By following this structured approach, joserfc ensures clarity, ease of understanding, and simplicity in both comprehension and utilization of the library. New RFCs -------- To add a new RFC implementation to ``joserfc``, you can follow a straightforward approach: 1. Create a new folder within the ``joserfc`` package, named after the RFC number. 2. Place the relevant code files and modules related to the new RFC within the created folder. 3. Organize the code structure within the folder to align with the RFC's specifications and guidelines. 4. Update the necessary high-level APIs (``jws.py``, ``jwe.py``, ``jwk.py``, ``jwt.py``) to integrate and expose the new RFC implementation. By adhering to this approach, you can easily incorporate new RFC implementations into ``joserfc``, maintaining a well-organized and extensible codebase. Draft RFCs ---------- Draft RFCs are specifications that are still in the draft phase and subject to potential changes. In ``joserfc``, draft implementations are placed within the ``joserfc.drafts`` package. It's important to note that draft implementations are not typically accepted as part of the main ``joserfc`` library until the RFC is officially published and stabilized. Although draft implementations are included within the ``joserfc.drafts`` package for exploration and experimentation purposes, they may not fully adhere to the final version of the RFC. It is recommended to use caution when relying on draft implementations, as they may undergo significant changes or be incompatible with the final RFC specification. joserfc-1.1.0/docs/contributing/translation.rst000066400000000000000000000022451501424510600216740ustar00rootroot00000000000000:description: Help us translating this documentation into other languages. Translations ============ To begin translating this documentation into other languages, please start by referring to the :ref:`development` guide, which will help you set up a suitable development environment. Afterward, navigate to the docs folder using the following command: .. code-block:: shell cd docs Generate .pot files ------------------- Before creating translations in your desired languages, you need to generate the source ``.pot`` files. This can be accomplished using the following command: .. code-block:: shell sphinx-build -b gettext . _build/gettext Update languages ---------------- Next, proceed to generate the ``.po`` files in your preferred languages using the ``sphinx-intl`` tool: .. code-block:: shell sphinx-intl update -p _build/gettext -l de In this example, we're using the language code ``de`` to represent German. Writing the Translations ------------------------ Following the previous command, the ``.po`` files will be generated within the ``locales/de/LC_MESSAGES`` directory. You can now edit these files to add the German translations accordingly. joserfc-1.1.0/docs/guide/000077500000000000000000000000001501424510600151675ustar00rootroot00000000000000joserfc-1.1.0/docs/guide/algorithms.rst000066400000000000000000000163141501424510600200770ustar00rootroot00000000000000:description: All available algorithms for JWS, JWE, JWK, and JWT. .. _jwa: Algorithms ========== .. rst-class:: lead All available algorithms for JWS, JWE, JWK, and JWT. ----- This documentation describes the algorithms to be used with JSON Web Signature (JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK). JSON Web Key ------------ The JSON Web Key (JWK) algorithms contains: - :ref:`OctKey` : accepts key size in bits, which means the ``key_size`` MUST be dividable by 8. - :ref:`RSAKey` : accepts key size in bits, ``key_size`` MUST ``>=512`` and dividable by 8. - :ref:`ECKey` : accepts ``crv`` with ``P-256``, ``P-384``, ``P-521``, and ``secp256k1``. - :ref:`OKPKey` : accepts ``crv`` with ``Ed25519``, ``Ed448``, ``X25519``, and ``X448``. .. _jws_algorithms: JSON Web Signature ------------------ ``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, and RFC8812. You MUST specify the correct key type for each algorithm. ============== ========== ================== Algorithm name Key Type Recommended ============== ========== ================== none OctKey :bdg-danger:`No` HS256 OctKey :bdg-success:`Yes` HS384 OctKey :bdg-danger:`No` HS512 OctKey :bdg-danger:`No` RS256 RSAKey :bdg-success:`Yes` RS384 RSAKey :bdg-danger:`No` RS512 RSAKey :bdg-danger:`No` ES256 ECKey :bdg-success:`Yes` ES384 ECKey :bdg-danger:`No` ES512 ECKey :bdg-danger:`No` PS256 RSAKey :bdg-danger:`No` PS384 RSAKey :bdg-danger:`No` PS512 RSAKey :bdg-danger:`No` EdDSA OKPKey :bdg-danger:`No` ES256K ECKey :bdg-danger:`No` ============== ========== ================== .. note:: ``EdDSA`` algorithm only accepts ``OKPKey`` with "crv" of "Ed25519" and "Ed448". By default, JWS ``serialize`` and ``deserialize`` methods will ONLY allow recommended algorithms. To use non-recommended algorithms, developers MUST explicitly specify the algorithms either by the ``algorithms`` parameter, or ``registry`` parameter. .. code-block:: python from joserfc import jws from joserfc.jwk import OctKey key = OctKey.import_key("secret") # HS384 is a non-recommended algorithm jws.serialize_compact({"alg": "HS384"}, b"payload", key, algorithms=["HS384"]) # or with a custom registry registry = jws.JWSRegistry(algorithms=["HS384"]) jws.serialize_compact({"alg": "HS384"}, b"payload", key, registry=registry) .. _jwe_algorithms: JSON Web Encryption ------------------- ``joserfc.jwe`` module supports algorithms from RFC7518, and drafts of ``ECDH-1PU``. You MUST specify the correct key type for each algorithm. =================== ========== ================== Algorithm name Key Type Recommended =================== ========== ================== dir OctKey :bdg-success:`Yes` A128KW OctKey :bdg-success:`Yes` A192KW OctKey :bdg-danger:`No` A256KW OctKey :bdg-success:`Yes` RSA1_5 RSAKey :bdg-danger:`No` RSA-OAEP RSAKey :bdg-success:`Yes` RSA-OAEP-256 RSAKey :bdg-danger:`No` ECDH-ES ECKey :bdg-success:`Yes` ECDH-ES+A128KW ECKey :bdg-success:`Yes` ECDH-ES+A192KW ECKey :bdg-danger:`No` ECDH-ES+A256KW ECKey :bdg-success:`Yes` A128GCMKW OctKey :bdg-danger:`No` A192GCMKW OctKey :bdg-danger:`No` A256GCMKW OctKey :bdg-danger:`No` PBES2-HS256+A128KW RSAKey :bdg-danger:`No` PBES2-HS384+A192KW RSAKey :bdg-danger:`No` PBES2-HS512+A256KW RSAKey :bdg-danger:`No` =================== ========== ================== All algorithms defined in RFC7518 for "enc" value are recommended, which including: - ``A128CBC-HS256`` - ``A192CBC-HS384`` - ``A256CBC-HS512`` - ``A128GCM`` - ``A192GCM`` - ``A256GCM`` A ``DEF`` algorithm for the "zip" (compression) header parameter is also defined in RFC7518, which is recommended. There are also additional algorithms for "alg" and "enc" in draft versions. Please refer to the following sections for more information. OKPKey ~~~~~~ You can use ``OKPKey`` with the "crv" (curve) parameter set to ``X25519`` or ``X448`` for the following algorithms: - ECDH-ES - ECDH-ES+A128KW - ECDH-ES+A192KW - ECDH-ES+A256KW This allows you to utilize these elliptic curve algorithms with ``OKPKey`` for your cryptographic operations. .. _chacha20: C20P and XC20P ~~~~~~~~~~~~~~ ``C20P`` and ``XC20P`` algorithms are still in drafts, they are not registered by default. To use ``C20P`` and ``XC20P``, developers have to install the ``PyCryptodome`` module. .. code-block:: shell pip install pycryptodome This is caused by ``cryptography`` package does only support "ChaCha20" cipher, not **XChaCha20**, while ``pycryptodome`` supports both "ChaCha20" and "XChaCha20" ciphers. Register ciphers ++++++++++++++++ The default :ref:`registry` doesn't contain draft ciphers, developers MUST register ``C20P`` and ``XC20P`` at first: .. code-block:: python from joserfc.drafts.jwe_chacha20 import register_chaha20_poly1305 register_chaha20_poly1305() Use custom ``registry`` +++++++++++++++++++++++ .. module:: joserfc.jwe :noindex: Use a custom ``registry`` in :meth:`encrypt_compact`, :meth:`decrypt_compact`, :meth:`encrypt_json`, and :meth:`decrypt_json`. .. code-block:: python from joserfc import jwe from joserfc.jwk import OctKey registry = jwe.JWERegistry( # add more "alg" and "enc" if you want algorithms=["A128KW", "C20P"] ) key = OctKey.generate_key(128) # A128KW requires 128 bits key protected = {"alg": "A128KW", "enc": "C20P"} encrypted_text = jwe.encrypt_compact( protected, b"hello", public_key=key, registry=registry, ) .. _ecdh1pu: ECDH-1PU algorithms ~~~~~~~~~~~~~~~~~~~ Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model (ECDH-1PU) are still in drafts, they are not registered by default. To use ``ECDH-1PU`` related algorithms, developers MUST register them manually: .. code-block:: python from joserfc.drafts.jwe_ecdh_1pu import register_ecdh_1pu register_ecdh_1pu() Then use a custom ``registry`` with the required ``ECDH-1PU`` algorithms. For instance: .. code-block:: python from joserfc import jwe from joserfc.jwk import ECKey registry = jwe.JWERegistry( algorithms=["ECDH-1PU+A128KW", "A128CBC-HS256"] ) protected = {"alg": "ECDH-1PU+A128KW", "enc": "A128CBC-HS256"} recipient_key = ECKey.import_key("your-ec-public-key.json") sender_key = ECKey.import_key("your-ec-sender-key.json") # this SHOULD be a private key encrypted_text = jwe.encrypt_compact( protected, b"hello", public_key=recipient_key, registry=registry, sender_key=sender_key, ) .. important:: The ``ECDH-1PU`` algorithms require a **sender key**, which MUST be a private key when calling :meth:`encrypt_compact` and :meth:`encrypt_json` methods. The ``sender_key`` can be a :class:`~joserfc.jwk.KeySet`, and JWE will find the correct key according to ``skid`` header value. joserfc-1.1.0/docs/guide/index.rst000066400000000000000000000047751501424510600170450ustar00rootroot00000000000000:description: Get started with joserfc module to encode and decode JSON Web Token (JWT). Guide ===== This section provides a quick overview of how to get started with ``joserfc`` and perform encoding and decoding a JWT. Encode and decode JWT --------------------- .. code-block:: python >>> from joserfc import jwt >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> encoded_jwt = jwt.encode({"alg": "HS256"}, {"key": "value"}, key) >>> encoded_jwt 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ2YWx1ZSJ9.FG-8UppwHaFp1LgRYQQeS6EDQF7_6-bMFegNucHjmWg' >>> token = jwt.decode(encoded_jwt, key) >>> token.header {'alg': 'HS256', 'typ': 'JWT'} >>> token.claims {'key': 'value'} >>> claims_requests = jwt.JWTClaimsRegistry() >>> claims_requests.validate(token.claims) Learn the details of :ref:`jwt` in the next chapter. Import and generate JWK ----------------------- .. code-block:: python >>> from joserfc.jwk import RSAKey >>> rsa_key = RSAKey.generate_key(512) >>> rsa_key.as_pem(private=True) b'-----BEGIN PRIVATE KEY-----\n....' >>> rsa_key.as_pem(private=False) b'-----BEGIN PUBLIC KEY-----\n...' >>> rsa_key.as_dict(private=False) { 'n': 's6DoAL_A4EZ9pQFemuFtUPxjuPxyZC_1_...', 'e': 'AQAB', 'kty': 'RSA', 'kid': 'Y9-Lx9yk...' } .. code-block:: python >>> from joserfc.jwk import RSAKey >>> f = open("your-rsa-key.pem") >>> pem_data = f.read() >>> pem_data '-----BEGIN PUBLIC KEY-----\n...' >>> rsa_key = RSAKey.import_key(pem_data) >>> rsa_key.as_pem() b'-----BEGIN PUBLIC KEY-----\n...' Learn the details of :ref:`jwk` in the next chapter. Dive deep --------- Next, learn each module in details. .. grid:: 2 :gutter: 2 :padding: 0 .. grid-item-card:: JSON Web Key :link-type: ref :link: jwk Learn how to use ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and JSON Web Key Set. .. grid-item-card:: JSON Web Token :link-type: ref :link: jwt JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`. .. grid-item-card:: JSON Web Signature :link-type: ref :link: jws Most :ref:`jwt` are encoded with JWS in compact serialization. .. grid-item-card:: JSON Web Encryption :link-type: ref :link: jwe JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures. .. toctree:: :hidden: jwk jwt jws jwe joserfc-1.1.0/docs/guide/introduction.rst000066400000000000000000000036721501424510600204520ustar00rootroot00000000000000:description: Introduction of joserfc, and why it is created. Introduction ============ ``joserfc`` is a Python library that provides a comprehensive implementation of several essential JSON Object Signing and Encryption (JOSE) standards. Derived from Authlib_, ``joserfc`` offers a redesigned API specifically tailored to JOSE functionality, making it easier for developers to work with JWS, JWE, JWK, JWA, and JWT in their Python applications. .. _Authlib: https://authlib.org/ Features -------- - **Python Type Hints**: ``joserfc`` takes advantage of Python's type hinting capabilities, providing a more expressive and readable codebase. The use of type hints enhances development workflows by enabling better static analysis, improved IDE support, and more reliable code refactoring. - **Organized Codebase with RFC Compliance**: ``joserfc`` is structured following the RFC standards, ensuring clear separation and organization of the different JOSE functionalities. It strictly follows the latest versions of the JOSE standards, guaranteeing the highest level of interoperability and compliance. Why joserfc? ------------ ``joserfc`` is derived from Authlib to facilitate easy maintenance and modularity. Previously, Authlib was developed as a mono library to design a comprehensive API that covered a wide range of authentication and security needs. However, as the project evolved, it became evident that splitting the modules from Authlib would improve maintainability and provide more focused and specialized libraries. With ``joserfc``, developers can now benefit from a standalone library dedicated specifically to JOSE standards. This focused approach allows for better code organization, improved documentation, and a more streamlined development experience. By utilizing ``joserfc``, developers can confidently integrate JOSE functionalities into their projects, knowing that they are working with a dedicated and well-maintained solution. joserfc-1.1.0/docs/guide/jwe.rst000066400000000000000000000167331501424510600165200ustar00rootroot00000000000000:description: How to encrypt and decrypt JWE in Compact, General JSON, and Flattened JSON Serialization. .. _jwe: JSON Web Encryption =================== .. module:: joserfc :noindex: JSON Web Encryption (JWE) represents encrypted content using JSON-based data structures. (via RFC7516_) .. _RFC7516: https://www.rfc-editor.org/rfc/rfc7516 Compact Encryption ------------------ The JWE Compact Serialization represents encrypted content as a compact, URL-safe string. This string is: .. code-block:: none BASE64URL(UTF8(JWE Protected Header)) || '.' || BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector) || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag) An example of a compact serialization (line breaks for display purposes only): .. code-block:: none eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ. OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8 1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi 6UklfCpIMfIjf7iGdXKHzg. 48V1_ALb6US04U3b. 5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji SdiwkIr3ajwQzaBtQD_A. XFBoMYUZodetZdvTiFvSkQ Encryption ~~~~~~~~~~ You can call :meth:`jwe.encrypt_compact` to construct a compact JWE serialization: .. code-block:: python from joserfc import jwe from joserfc.jwk import OctKey protected = {"alg": "A128KW", "enc": "A128GCM"} key = OctKey.generate_key(128) # algorithm requires key of big size 128 data = jwe.encrypt_compact(protected, "hello", key) A compact JWE is constructed by ``protected`` header, ``plaintext`` and a public key. In the above example, ``protected`` is the "protected header" part, `"hello"` is the plaintext part, and ``key`` is the public key part (oct key is a symmetric key, it is a shared key, there is no public or private differences). It is suggested that you learn the :ref:`jwk` section, and find the correct key type according to :ref:`JSON Web Encryption Algorithms `. Decryption ~~~~~~~~~~ It is very easy to decrypt the compact serialization in the previous example with :meth:`jwe.decrypt_compact`: .. code-block:: python obj = jwe.decrypt_compact(data, key) # obj.protected => {"alg": "A128KW", "enc": "A128GCM"} # obj.plaintext => b"hello" .. note:: If the algorithm is accepting an asymmetric key, you MUST use a private key in ``decrypt_compact`` method. JSON Encryption --------------- The JWE JSON Serialization represents encrypted content as a JSON object. This representation is neither optimized for compactness nor URL safe. An example of a JWE using the general JWE JSON Serialization is as follows: .. code-block:: none { "protected":"", "unprotected":, "recipients":[ {"header":, "encrypted_key":""}, ... {"header":, "encrypted_key":""}], "aad":"", "iv":"", "ciphertext":"", "tag":"" } Encryption ~~~~~~~~~~ .. versionchanged:: 0.6.0 ``jwe.JSONEncryption`` is separated to ``GeneralJSONEncryption`` and ``FlattenedJSONEncryption``. The structure for JSON JWE serialization is a little complex, developers SHOULD create an object of :class:`jwe.GeneralJSONEncryption` at first: .. code-block:: python from joserfc.jwk import OctKey, RSAKey from joserfc.jwe import GeneralJSONEncryption, encrypt_json obj = GeneralJSONEncryption({"enc": "A128GCM"}, b"hello") # add first recipient with alg of A128KW key1 = OctKey.generate_key(128) obj.add_recipient({"alg": "A128KW"}, key1) # add second recipient with alg of RSA-OAEP key2 = RSAKey.generate_key() # the alg requires RSAKey obj.add_recipient({"alg": "RSA-OAEP"}, key2) # since every recipient has recipient key, # there is no need to pass a public key parameter encrypt_json(obj, None) If you prefer adding recipient keys from existing key set: .. code-block:: python import json from joserfc.jwk import KeySet with open("your-jwks.json") as f: data = json.load(f) key_set = KeySet.import_key_set(data) # then add each recipient with ``kid`` obj.add_recipient({"alg": "A128KW", "kid": "oct-key-id"}) obj.add_recipient({"alg": "RSA-OAEP", "kid": "rsa-key-id"}) # then pass the key set as the ``key`` parameter encrypt_json(obj, key_set) Decryption ~~~~~~~~~~ Calling :meth:`jwe.decrypt_json` could decrypt the JSON Serialization in the above example. Most of the time, you would need a JWK Set of private keys for decryption. .. code-block:: python import json from joserfc import jwe from joserfc.jwk import KeySet with open("your-private-jwks.json") as f: data = json.load(f) key_set = KeySet.import_key_set(data) def parse_jwe(data): # this data is a dict of JWE JSON Serialization jwe.decrypt_json(data, key_set) By default, ``jwe.decrypt_json`` will validate all the recipients, if one recipient validation fails, the method will raise an error. You can also change the default behavior to bypass the decryption with only one recipient get verified: .. code-block:: python registry = JWERegistry(verify_all_recipients=False) jwe.decrypt_json(data, key_set, registry=registry) General and Flattened ~~~~~~~~~~~~~~~~~~~~~ The above example is a General JWE JSON Serialization, there is also a Flattened JWE JSON Serialization. The Flattened one MUST ONLY contain one recipient. The syntax of a JWE using the flattened JWE JSON Serialization is as follows: .. code-block:: none { "protected":"", "unprotected":, "header":, "encrypted_key":"", "aad":"", "iv":"", "ciphertext":"", "tag":"" } It is flattened, it moves all the members out of the ``recipients`` field. To ``encrypt_json`` into a flattened serialization, you can construct a :class`jwe.FlattenedJSONEncryption` instead: .. code-block:: python obj = FlattenedJSONEncryption(protected, plaintext) And make sure only adding one recipient. Algorithms & Registry --------------------- ``joserfc.jwe`` module would ONLY allow recommended algorithms by default, you can find which algorithm is recommended according to :ref:`JSON Web Encryption Algorithms `. It is possible to support non-recommended algorithms by passing the ``algorithms`` parameter, or with a custom ``registry``. .. code-block:: python jwe.encrypt_compact(protected, plaintext, key, algorithms=["A128GCM", "A128KW"]) registry = JWERegistry(algorithms=["A128GCM", "A128KW"]) jwe.encrypt_compact(protected, plaintext, key, registry=registry) The registry is a little complex, find out more on the :ref:`registry` section. joserfc-1.1.0/docs/guide/jwk.rst000066400000000000000000000267751501424510600165350ustar00rootroot00000000000000:description: Usage of OctKey, RSAKey, ECKey, and OKPKey. .. _jwk: JSON Web Key ============ .. module:: joserfc.jwk :noindex: A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key (via RFC7517_). .. _RFC7517: https://www.rfc-editor.org/rfc/rfc7517 .. _OctKey: OctKey ------ An :class:`OctKey` is a symmetric key defined in `RFC7518 section 6.4 `_. Create an "oct" key ~~~~~~~~~~~~~~~~~~~ You can generate an ``OctKey`` with the :meth:`OctKey.generate_key` method: .. code-block:: python from joserfc.jwk import OctKey key_size = 256 # in bit size, 256 equals 32 bytes key = OctKey.generate_key(key_size) Import an "oct" key ~~~~~~~~~~~~~~~~~~~ You can import an ``OctKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import OctKey OctKey.import_key("a-random-string-as-key") OctKey.import_key(b"a-random-bytes-as-key") OctKey.import_key({ "kty": "oct", "k": "Zm9v", }) When importing a key, you can add extra parameters into a key: .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("foo", {"use": "sig"}) >>> key.as_dict() {'k': 'Zm9v', 'use': 'sig', 'kty': 'oct'} .. _RSAKey: RSAKey ------ An :class:`RSAKey` is an asymmetric key defined in `RFC7518 section 6.3 `_. It represents RSA keys. Generate an "RSA" key ~~~~~~~~~~~~~~~~~~~~~ You can generate an "EC" key with a given key size (in bit): .. code-block:: python from joserfc.jwk import RSAKey key_size = 2048 key = RSAKey.generate_key(key_size) Import an "RSA" key ~~~~~~~~~~~~~~~~~~~ You can import an ``RSAKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import RSAKey pem_file = """ -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAm0tWm31IQ3zYU27bk/NZ 3wMJOJ+Moska3WqnptWyiVR+p/qCBlV18NUSwshoctTkETi8+HIhOjUPb0WRvQV0 YcpsqBVdSuPZ3m4Q+uX/rudAoDKHJ6B7vwjfeg4w9aT/YF+Zi61tEy1c15rHKyXA HjSQGzIasOiXK1eSssim6Exx+caRL0/vWV8+0QICmEBVJiJyfDB4O3WXKac+QsI3 LM7ZjWqQFdvx3o1v7sDycz0zdpk4qEK7hEHUsYIsyYHb70iKSkiuo3nqq2HUHklW y322djy/IqEq03KWuePRUZdPTDzlx5qyKpVLpMswYporngvXKpMTCal5HYfAGuYS MuOAVa1oL1gX8W+N4+XNrVCHSCh1JHjnO2qUT6em/HJ2gERj3kZDDfE6UXVjAw2i US2lP+GEim3AdUQ1jTO27Vjvuv+rNk7UjL8iDW1THlvYI9AeQnqtTTBib2b5+k6a 8AzSPhMX/F7WP9hf0NUbkYyrJ7zRfERKqLrwpZu83PRWclnB6afPIZcN58uc+4J5 516Ryk6PUawbBHj6zfSIDEuwKj71ki+t0GHaG4RO9QFk75ArsHWrRZNQhELBVep/ ohwl4vscRMQFgdwdzZN8ZaaJRPFih7B+YiwIhuxpAF9fPrETa6UGoBK6MlWKE6EZ i5YRKx6rVWvFfMWAV3Tx9uECAwEAAQ== -----END PUBLIC KEY----- """ RSAKey.import_key(pem_file) RSAKey.import_key({ "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": "n4EPtAOCc9AlkeQHPzHSt...", "e": "AQAB", "d": "bWUC9B-...", "q": "uKE2dh-...", "dp": "B8PV...", "dq": "CLDm...", "qi": "3PiFU4..." }) .. _ECKey: ECKey ----- An :class:`ECKey` is an asymmetric key defined in `RFC7518 section 6.2 `_. It represents Elliptic Curve [DSS] keys. Generate an "EC" key ~~~~~~~~~~~~~~~~~~~~ You can generate an "EC" key with the given curve: .. code-block:: python from joserfc.jwk import ECKey key = ECKey.generate_key("P-256") The "crv" values that :class:`ECKey` supports: - ``P-256`` via RFC7518 - ``P-384`` via RFC7518 - ``P-521`` via RFC7518 - ``secp256k1`` via RFC8812 .. hint:: It is ``P-521``, not ``P-512``, it is not a typo. Import an "EC" key ~~~~~~~~~~~~~~~~~~ You can import an ``ECKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import ECKey pem_file = """ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIBnRS4Tf1PY6Jb7QOwAM7OWUOMJTBenEWRvGBCGgctBfoAoGCCqGSM49 AwEHoUQDQgAE3r15c+Yd+0GXKysfWtwkqF7k12ylNE9LdfRP4TfkUcJSQXyGQjcx U8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END EC PRIVATE KEY----- """ ECKey.import_key(pem_file) ECKey.import_key({ "kty": "EC", "crv": "P-256", "x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", "y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", "d": "Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg" }) .. _OKPKey: OKPKey ------ An :class:`OKPKey` is an asymmetric key defined in RFC8037_ CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE). .. _RFC8037: https://www.rfc-editor.org/rfc/rfc8037#section-2 Generate an "OKP" key ~~~~~~~~~~~~~~~~~~~~~ You can generate an "OKP" key with the given curve: .. code-block:: python from joserfc.jwk import OKPKey key = OKPKey.generate_key("Ed25519") :class:`OKPKey` accepts "crv" values of ``Ed25519``, ``Ed448``, ``X25519``, and ``X448``. Import an "OKP" key ~~~~~~~~~~~~~~~~~~~ You can import an ``OKPKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import OKPKey pem_file = """ -----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOaVsPKMXOBfq9aHlDEaMlBY+FR63hwrINHa2X74uHXUr 3/VXE8eMhrr8stXn41CQKqVmFEeL5Uj5Gg== -----END PRIVATE KEY----- """ OKPKey.import_key(pem_file) OKPKey.import_key({ "kty": "OKP", "crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "d": "gUF17HCe-pbN7Ej2rDSXl-e7uSj7rQW5u2dNu0KINP0", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y" }) Key Set ------- A JWK Set is a JSON object that represents a set of JWKs. An example of a JWK Set: .. code-block:: none {"keys": [ { "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" }, { "kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx...", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29" } ]} Create a key set ~~~~~~~~~~~~~~~~ You can create a key set with a given set of keys: .. code-block:: python from joserfc.jwk import KeySet key_set = KeySet([rsa_key1, rsa_key2, ec_key1]) Or, you can generate a key set for a certain "kty": .. code-block:: python key_set = KeySet.generate_key_set("EC", "P-256", count=4) Import a key set ~~~~~~~~~~~~~~~~ An example about importing JWKS from a local file: .. code-block:: python import json with open("your-jwks.json") as f: data = json.load(f) key_set = KeySet.import_key_set(data) An example about importing JWKS from a URL: .. code-block:: python import requests resp = requests.get("https://example.com/jwks.json") key_set = KeySet.import_key_set(resp.json()) Key methods ----------- .. _thumbprint: ``thumbprint`` ~~~~~~~~~~~~~~ Call this method will generate the thumbprint with algorithm defined in RFC7638. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("foo") >>> key.thumbprint() '8-e-qGDS2nDpfZzOPtD8Sb7NkifUbw70MeqOKIqyaRw' .. _ensure_kid: ``ensure_kid`` ~~~~~~~~~~~~~~ Call this method to make sure the key contains a ``kid``. If the key has no ``kid``, generate one with the above ``.thumbprint`` method. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("foo") >>> key.kid None >>> key.ensure_kid() >>> key.kid '8-e-qGDS2nDpfZzOPtD8Sb7NkifUbw70MeqOKIqyaRw' ``as_dict`` ~~~~~~~~~~~ Dump a key or key set into dict format, which can be used to convert to JSON: .. code-block:: python data = key.as_dict(private=False) # dump as a public key # data = key.as_dict(private=True) # dump as a private key with open("my-key.json", "w") as f: json.dump(data, f) ``as_pem`` ~~~~~~~~~~ Dump an asymmetric key into PEM format (in bytes): .. code-block:: python # text = key.as_pem(public=True) # dump as a public key text: bytes = key.as_pem(private=True) # dump as a private key with open("my-key.pem", "w") as f: f.write(text) ``as_der`` ~~~~~~~~~~ Dump an asymmetric key into DER format (in bytes): .. code-block:: python # text = key.as_der(public=True) # dump as a public key text: bytes = key.as_der(private=True) # dump as a private key with open("my-key.der", "w") as f: f.write(text) ``JWKRegistry`` --------------- The :class:`JWKRegistry` class serves as a registry for storing all the supported key types in the ``joserfc`` library. While developers typically use specific key types such as ``RSAKey`` or ``ECKey``, this registry offers a means to dynamically import and generate keys. Import keys ~~~~~~~~~~~ The :meth:`JWKRegistry.import_key` can choose the correct key type automatically when importing a JWK in dict: .. code-block:: python data = {"kty": "oct", "k": "..."} key = JWKRegistry.import_key(data) # returns a OctKey data = {"kty": "RSA", ...} key = JWKRegistry.import_key(data) # returns a RSAKey data = {"kty": "EC", ...} key = JWKRegistry.import_key(data) # returns a ECKey data = {"kty": "OKP", ...} key = JWKRegistry.import_key(data) # returns a OKPKey If the key is in bytes or string, not dict, developers SHOULD specify the key type manually: .. code-block:: python data = b"---- BEGIN RSA PRIVATE KEY ----\n..." key = JWKRegistry.import_key(data, "RSA") .. versionadded:: v1.1.0 You can use ``jwk.import_key`` directly. For example:: from joserfc.jwk import import_key data = {"kty": "oct", "k": "..."} key = import_key(data) Generate keys ~~~~~~~~~~~~~ The :meth:`JWKRegistry.generate_key` can generate a key with all the supported key types. For ``oct`` and ``RSA`` the parameters in this method: .. code-block:: python # (key_type: str, size: int, parameters: Optional[dict], private: bool=True) key = JWKRegistry.generate_key("oct", 256) key = JWKRegistry.generate_key("RSA", 2048, {"use": "sig"}) For ``EC`` and ``OKP`` keys, the parameters are: .. code-block:: python # (key_type: str, crv: str, parameters: Optional[dict], private: bool=True) key = JWKRegistry.generate_key("EC", "P-256") key = JWKRegistry.generate_key("OKP", "Ed25519") .. versionadded:: v1.1.0 You can use ``jwk.generate_key`` directly. For example:: from joserfc.jwk import generate_key key = generate_key("oct", 256) Options ------- The ``import_key`` and ``generate_key`` methods available in ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and ``JWKRegistry`` classes have an optional ``parameters`` parameter. This ``parameters`` allows you to provide a dict that includes additional key parameters to be included in the JWK. Some of the standard (registered) header fields are: - ``kty``: Key Type, it is automatically added - ``use``: Public Key Use, "sig" or "enc" - ``key_ops``: Key Operations, allowed operations of this key - ``alg``: Algorithm, allowed algorithm of this key - ``kid``: Key ID, a string of the key ID When using ``import_key`` and ``generate_key``, developers can pass the extra key ``parameters``: .. code-block:: python parameters = {"use": "sig", "alg": "RS256", "key_ops": ["verify"]} RSAKey.import_key(data, parameters=parameters) The above ``RSAKey`` then can only be used for ``JWS`` with ``alg`` of ``RS256``, and it can only be used for deserialization (``verify``). joserfc-1.1.0/docs/guide/jws.rst000066400000000000000000000321751501424510600165340ustar00rootroot00000000000000:description: How to serialize and deserialize JWS in Compact, General JSON, and Flattened JSON Serialization. .. _jws: JSON Web Signature ================== .. module:: joserfc :noindex: JSON Web Signature (JWS) represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. (via RFC7515_) .. _RFC7515: https://www.rfc-editor.org/rfc/rfc7515 Compact Signature ----------------- The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string. This string is: .. code-block:: text BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature) An example of a compact serialization (line breaks for display purposes only): .. code-block:: text eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9. eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt cGxlLmNvbS9pc19yb290Ijp0cnVlfQ. dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk Serialization ~~~~~~~~~~~~~ You can call :meth:`jws.serialize_compact` to construct a compact JWS serialization: .. code-block:: python from joserfc import jws from joserfc.jwk import OctKey key = OctKey.import_key("secret") protected = {"alg": "HS256"} jws.serialize_compact(protected, "hello", key) # => 'eyJhbGciOiJIUzI1NiJ9.aGVsbG8.UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A' A compact JWS is constructed by protected header, payload and a private key. In the above example, ``protected`` is the "protected header" part, `"hello"` is the payload part, and `"secret"` is a plain private key. Deserialization ~~~~~~~~~~~~~~~ Calling :meth:`jws.deserialize_compact` to extract and verify the compact serialization with a public key. .. code-block:: python from joserfc import jws from joserfc.jwk import OctKey text = "eyJhbGciOiJIUzI1NiJ9.aGVsbG8.UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A" key = OctKey.import_key("secret") obj = jws.deserialize_compact(text, key) # obj.protected => {"alg": "HS256"} # obj.payload => b"hello" JSON Signature -------------- The JWS JSON Serialization represents digitally signed or MACed content as a JSON object. This representation is neither optimized for compactness nor URL-safe. An example of a JSON serialization: .. code-block:: json { "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures": [ { "protected": "eyJhbGciOiJSUzI1NiJ9", "header": {"kid":"2010-12-29"}, "signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw" }, { "protected": "eyJhbGciOiJFUzI1NiJ9", "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] } Serialization ~~~~~~~~~~~~~ You can call :meth:`jws.serialize_json` to construct a JSON JWS serialization: .. code-block:: python import json from joserfc import jws from joserfc.jwk import KeySet members = [ { "protected": {"alg": "RS256"}, "header": {"kid": "2010-12-29"}, }, { "protected": {"alg": "ES256"}, "header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"}, }, ] payload = b'{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}' with open("your-private-jwks.json") as f: data = json.load(f) # this key set SHOULD contains kid of "2010-12-29" # and "e9bc097a-ce51-4036-9562-d2ade882db0d" private_key_set = KeySet.import_key_set(data) value = jws.serialize_json(members, payload, private_key_set) #: this ``value`` is a dict which looks like the example above The JSON JWS serialization is constructed by members, payload and private key. A **member** is a combination of protected header and public header: .. code-block:: python member = { "protected": {"alg": "RS256"}, "header": {"kid": "2010-12-29"}, } The ``protected`` header will be base64 encoded in the JSON serialization, together with the payload to sign a signature for the member: .. code-block:: none SIGNATURE INPUT = BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) SIGNATURE = BASE64URL(SignMethod(SIGNATURE INPUT, Private Key)) In the above example, we passed a :class:`jwk.KeySet` as the private key parameter, the :meth:`jws.serialize_json` will find the correct key in the key set by ``kid``. Deserialization ~~~~~~~~~~~~~~~ Calling :meth:`jws.deserialize_json` to extract and verify the JSON serialization with a public key. .. code-block:: python with open("your-public-jwks.json") as f: data = json.load(f) # the public pair of your previous private key set public_key_set = KeySet.import_key_set(data) # value is the generated by above code obj = jws.deserialize_json(value, public_key_set) # => assert obj.payload == payload General and Flattened ~~~~~~~~~~~~~~~~~~~~~ There are two types of JSON JWS serializations, "general" and "flattened". The above example is a General JSON Serialization. A Flattened JSON Serialization contains only one member. Compare the below examples: .. code-block:: json :caption: Flattened JSON Serialization { "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "protected": "eyJhbGciOiJFUzI1NiJ9", "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } .. code-block:: json :caption: General JSON Serialization { "payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ", "signatures": [ { "protected": "eyJhbGciOiJFUzI1NiJ9", "header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"}, "signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" } ] } You can pass a member dict to construct a flattened serialization; and a list of members to construct a general serialization: .. code-block:: python member = { "protected": {"alg": "ES256"}, "header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"}, } # flattened jws.serialize_json(member, payload, private_key) # general jws.serialize_json([member], payload, private_key) The returned value from ``deserialize_json`` is an object of :class:`jws.GeneralJSONSignature` or :class:`jws.FlattenedJSONSignature`, you can tell if the signature is flattened or general with ``obj.flattened``: .. versionchanged:: 0.6.0 ``jws.JSONSignature`` is separated to ``GeneralJSONSignature`` and ``FlattenedJSONSignature``. .. code-block:: python obj = jws.deserialize_json(data, public_key) if obj.flattened: print("Flattened JSON Serialization") else: print("General JSON Serialization") Algorithms ---------- ``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, and RFC8812. Here lists all the algorithms ``joserfc.jws`` supporting: ============== ================================================ ================== Algorithm name Description Recommended ============== ================================================ ================== none No digital signature or MAC performed :bdg-danger:`No` HS256 HMAC using SHA-256 :bdg-success:`YES` HS384 HMAC using SHA-384 :bdg-danger:`No` HS512 HMAC using SHA-512 :bdg-danger:`No` RS256 RSASSA-PKCS1-v1_5 using SHA-256 :bdg-success:`YES` RS384 RSASSA-PKCS1-v1_5 using SHA-384 :bdg-danger:`No` RS512 RSASSA-PKCS1-v1_5 using SHA-512 :bdg-danger:`No` ES256 ECDSA using P-256 and SHA-256 :bdg-success:`YES` ES384 ECDSA using P-384 and SHA-384 :bdg-danger:`No` ES512 ECDSA using P-521 and SHA-512 :bdg-danger:`No` PS256 RSASSA-PSS using SHA-256 and MGF1 with SHA-256 :bdg-danger:`No` PS384 RSASSA-PSS using SHA-384 and MGF1 with SHA-384 :bdg-danger:`No` PS512 RSASSA-PSS using SHA-512 and MGF1 with SHA-512 :bdg-danger:`No` EdDSA Edwards-curve Digital Signature :bdg-danger:`No` ES256K ECDSA using secp256k1 curve and SHA-256 :bdg-danger:`No` ============== ================================================ ================== UnsupportedAlgorithmError ~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionchanged:: 1.1.0 From version 1.1.0, an ``UnsupportedAlgorithmError`` will be raised instead of a ``ValueError``. The serialization and deserialization methods on ``joserfc.jws`` module accept an ``algorithms`` parameter for specifying the allowed algorithms. By default, those ``serialize`` and ``deserialize`` methods will ONLY allow recommended algorithms defined by RFCs. With non recommended algorithms, you may encounter the below error. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> jws.serialize_compact({"alg": "HS384"}, b"payload", key) Traceback (most recent call last): File "", line 1, in File ".../joserfc/jws.py", line 112, in serialize_compact alg: JWSAlgModel = registry.get_alg(protected["alg"]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../joserfc/rfc7515/registry.py", line 60, in get_alg raise UnsupportedAlgorithmError(f'Algorithm of "{name}" is not recommended') joserfc.errors.UnsupportedAlgorithmError: unsupported_algorithm: Algorithm of "HS384" is not recommended ``joserfc`` does support ``HS384``, but this algorithm is not recommended by specifications, developers MUST explicitly specify the supported algorithms either by the ``algorithms`` parameter, or ``registry`` parameter. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, algorithms=["HS384"]) 'eyJhbGciOiJIUzM4NCJ9.cGF5bG9hZA.TJEvlp74g89hNRNGNZxCQvB7YDEAWP5vFAjgu1O9Qr5BLMj0NtvbxvYkVYPGp-xQ' Developers can also apply the ``registry`` parameter to resolve this issue. Here is an example of using :ref:`registry`. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> registry = jws.JWSRegistry(algorithms=["HS384"]) >>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, registry=registry) 'eyJhbGciOiJIUzM4NCJ9.cGF5bG9hZA.TJEvlp74g89hNRNGNZxCQvB7YDEAWP5vFAjgu1O9Qr5BLMj0NtvbxvYkVYPGp-xQ' .. _rfc7797: Unencoded Payload Option ------------------------ The unencoded payload option, defined in RFC7797, allows the payload of a JWS (JSON Web Signature) to remain unencoded, without using base64 encoding. To enable this option, you need to set the ``b64`` header parameter to ``false`` in the JWS header. To utilize the unencoded payload option in joserfc, you must import the serialize and deserialize methods from ``joserfc.rfc7797``. Here are examples demonstrating the usage of the ``b64`` option: .. code-block:: python from joserfc.rfc7797 import serialize_compact, deserialize_compact from joserfc.jwk import OctKey key = OctKey.import_key("secret") protected = {"alg": "HS256", "b64": False, "crit": ["b64"]} value = serialize_compact(protected, "hello", key) # => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.hello.mdPbZLtc3tqQ6NCV1pKF-qfEx-3jtR6rv109phKAc4I' deserialize_compact(value, key) .. note:: The ``crit`` MUST be present with ``"b64"`` in its value set when ``b64`` is in the header. Since the payload is not base64 encoded, if the payload contains non urlsafe characters, the compact serialization will detach the payload: .. code-block:: python from joserfc.rfc7797 import serialize_compact, deserialize_compact from joserfc.jwk import OctKey key = OctKey.import_key("secret") protected = {"alg": "HS256", "b64": False, "crit": ["b64"]} value = serialize_compact(protected, "$.02", key) # => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..GbtzAD3Cwe6snTZnaAxapwQz5QftEz7agx_6aMtZ4w0' # since the payload is detached, you need to specify the # payload when calling deserialize_compact deserialize_compact(value, key, payload="$.02") There are also methods for JSON serialization: ``serialize_json`` and ``deserialize_json``. joserfc-1.1.0/docs/guide/jwt.rst000066400000000000000000000266071501424510600165400ustar00rootroot00000000000000:description: How to encode and decode a JSON Web Token (JWT) in python. .. _jwt: JSON Web Token ============== .. module:: joserfc.jwt :noindex: JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe` and includes specific payload claims. These claims are required to be in JSON format and follow a predefined set of fields. .. hint:: Do you know that JSON Web Token (JWT) is not a part of JOSE. Instead, it was created by the OAuth working group. Encode token ------------ :meth:`encode` is the method for creating a JSON Web Token string. It encodes the payload with the given ``alg`` in header: .. code-block:: python from joserfc import jwt from joserfc.jwk import OctKey header = {"alg": "HS256"} claims = {"iss": "https://authlib.org"} key = OctKey.import_key("secret") text = jwt.encode(header, claims, key) The returned value of ``text`` in above example is: .. code-block:: none eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJpc3MiOiJodHRwczovL2F1dGhsaWIub3JnIn0. Zm430u0j1wzf5Me5Zoj2h6dTt9IFsb7-G5mUW3BTWbo Line breaks for display only. Decode token ------------ :meth:`decode` is the method to translate a JSON Web Token string into a token object which contains ``.header`` and ``.claims`` properties: .. code-block:: python # reuse the text and key in above example token = jwt.decode(text, key) # token.header = {'alg': 'HS256', 'typ': 'JWT'} # token.claims = {"iss": "https://authlib.org"} .. _claims: Validate claims --------------- The ``jwt.decode`` method will only verify if the payload is a JSON base64 string. You can define claims requests :class:`JWTClaimsRegistry` for validating the decoded claims. The ``JWTClaimsRegistry`` accepts each claim as an `Individual Claims Requests `_ JSON object. .. code-block:: python from joserfc.jwt import JWTClaimsRegistry claims_requests = JWTClaimsRegistry( iss={"essential": True, "value": "https://authlib.org"}, ) # usually you will use the claims registry after ``.decode`` claims_requests.validate(token.claims) The Individual Claims Requests JSON object contains: ``essential`` OPTIONAL. Indicates whether the Claim being requested is an Essential Claim. If the value is true, this indicates that the Claim is an Essential Claim. ``value`` OPTIONAL. Requests that the Claim be returned with a particular value. ``values`` OPTIONAL. Requests that the Claim be returned with one of a set of values, with the values appearing in order of preference. And we added one more field: ``allow_blank`` OPTIONAL. Allow essential claims to be an empty string. Missing essential claims ~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python claims_requests = JWTClaimsRegistry(aud={"essential": True}) # this will raise MissingClaimError claims = {"iss": "https://authlib.org"} claims_requests.validate(claims) # this will raise MissingClaimError claims = {"iss": ""} claims_requests.validate(claims) Allow empty essential claims ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python claims_requests = JWTClaimsRegistry(aud={"essential": True, "allow_blank": True}) # this will NOT raise MissingClaimError claims = {"iss": ""} claims_requests.validate(claims) Invalid claims values ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python claims = {"iss": "https://authlib.org"} claims_requests = JWTClaimsRegistry(iss={"value": "https://jose.authlib.org"}) claims_requests.validate(claims) # this will raise InvalidClaimError Default validators ~~~~~~~~~~~~~~~~~~ The ``JWTClaimsRegistry`` has built-in validators for timing related fields: - ``exp``: expiration time - ``nbf``: not before - ``iat``: issued at JWS & JWE --------- JWT is built on top of JWS and JWE, all of the above examples are in JWS. By default ``jwt.encode`` and ``jwt.decode`` work for **JWS**. To use **JWE**, you need to specify a ``registry`` parameter with ``JWERegistry``. Here is an example of JWE: .. code-block:: python from joserfc import jwt, jwe from joserfc.jwk import OctKey header = {"alg": "A128KW", "enc": "A128GCM"} claims = {"iss": "https://authlib.org"} key = OctKey.generate_key(128) # the algorithm requires key of 128 bit size registry = jwe.JWERegistry() # YOU MUST USE A JWERegistry jwt.encode(header, claims, key, registry=registry) The JWE formatted result contains 5 parts, while JWS only contains 3 parts, a JWE example would be something like this (line breaks for display only): .. code-block:: none eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwidHlwIjoiSldUIn0. F3plSTFE5GPJNs_qGsmoVx4o402URh5G. 57P7XX6C3hJbk-Nl. dpgaZFi3uI1RiOqI3bmYY3_opkljIwcByf_j6fM. uv1BZZy5F-ci54BS11EYGg Another difference is the key used for ``encode`` and ``decode``. For :ref:`jws`, a private key is used for ``encode``, and a public key is used for ``decode``. The ``encode`` method will use a private key to sign, and the ``decode`` method will use a public key to verify. For :ref:`jwe`, it is the contrary, a public key is used for ``encode``, and a private key is used for ``decode``. The ``encode`` method will use a public key to encrypt, and the ``decode`` method will use a private key to decrypt. The key parameter ----------------- In the above example, we're using :ref:`OctKey` only for simplicity. There are other types of keys in :ref:`jwk`. Key types ~~~~~~~~~ Each algorithm (``alg`` in header) requires a certain type of key. For example: - ``HS256`` requires ``OctKey`` - ``RS256`` requires ``RSAKey`` - ``ES256`` requires ``ECKey`` or ``OKPKey`` You can find the correct key type for each algorithm at: - :ref:`JSON Web Signature Algorithms ` - :ref:`JSON Web Encryption Algorithms ` Here is an example of a JWT with "alg" of ``RS256`` in JWS type: .. code-block:: python from joserfc import jwt from joserfc.jwk import RSAKey header = {"alg": "RS256"} claims = {"iss": "https://authlib.org"} with open("your-private-rsa-key.pem") as f: key = RSAKey.import_key(f.read()) # "RS256" is a recommended algorithm, no need to pass a custom ``registry`` text = jwt.encode(header, claims, key) # ``.encode`` for JWS type use a public key, if using a private key, # it will automatically extract the public key from private key jwt.decode(text, key) In production, ``jwt.encode`` is usually used by the *client* side, a client normally does not have the access to private keys. The server provider would usually expose the public keys in JWK Set. Use key set ~~~~~~~~~~~ You can also pass a JWK Set to the ``key`` parameter of :meth:`encode` and :meth:`decode` methods. .. code-block:: python import json from joserfc.jwk import KeySet from joserfc import jwt with open("your-private-jwks.json") as f: data = json.load(f) key_set = KeySet.import_key_set(data) header = {"alg": "RS256", "kid": "1"} claims = {"iss": "https://authlib.org"} jwt.encode(header, claims, key_set) The methods will find the correct key according to the ``kid`` you specified. If there is no ``kid`` in header, it will pick on randomly and add the ``kid`` of the key into header. A client would usually get the public key set from a public URL, normally the ``decode`` code would be something like: .. code-block:: python import requests from joserfc import jwt from joserfc.jwt import Token from joserfc.jwk import KeySet resp = requests.get("https://example.com/.well-known/jwks.json") key_set = KeySet.import_key_set(resp.json()) def parse_token(token_string: str) -> Token: return jwt.decode(token_string, key_set) Callable key ~~~~~~~~~~~~ It is also possible to assign a callable function as the ``key``: .. code-block:: python import json from joserfc.jwk import KeySet from joserfc.jws import CompactSignature def load_key(obj: CompactSignature) -> KeySet: headers = obj.headers() alg = headers["alg"] key_path = f"my-{alg}-keys.json" with open(key_path) as f: data = json.load(f) return KeySet.import_key_set(data) # jwt.encode(header, claims, load_key) Algorithms & Registry --------------------- The :meth:`encode` and :meth:`decode` accept an ``algorithms`` parameter for specifying the allowed algorithms. By default, it only allows your to use **recommended** algorithms. You can find out the recommended algorithms at: - :ref:`JSON Web Signature Algorithms ` - :ref:`JSON Web Encryption Algorithms ` For instance, ``HS386`` is not a recommended algorithm, and you want to use this algorithm: .. code-block:: python >>> from joserfc import jwt, jwk >>> header = {"alg": "HS384"} >>> claims = {"iss": "https://authlib.org"} >>> key = jwk.OctKey.import_key("secret") >>> jwt.encode(header, claims, key, algorithms=["HS384"]) If not specifying the ``algorithms`` parameter, the ``encode`` method will raise an error. JSON Encoder and Decoder ------------------------ .. versionadded:: 1.1.0 The parameters ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` for ``jwt.decode`` were introduced in version 1.1.0. When using ``jwt.encode``` to encode claims that contain data types that ``json`` module does not natively support, such as ``UUID`` and ``datetime``, an error will be raised. .. code-block:: python >>> import uuid >>> from joserfc import jwt, jwk >>> >>> key = jwk.OctKey.import_key("secret") >>> claims = {"sub": uuid.uuid4()} >>> jwt.encode({"alg": "HS256"}, claims, key) Traceback (most recent call last): File "", line 1, in File ".../joserfc/jwt.py", line 66, in encode payload = convert_claims(claims, encoder_cls) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../joserfc/rfc7519/claims.py", line 36, in convert_claims content = json.dumps(claims, ensure_ascii=False, separators=(",", ":"), cls=encoder_cls) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../lib/python3.12/json/__init__.py", line 238, in dumps **kw).encode(obj) ^^^^^^^^^^^ File ".../lib/python3.12/json/encoder.py", line 200, in encode chunks = self.iterencode(o, _one_shot=True) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File ".../lib/python3.12/json/encoder.py", line 258, in iterencode return _iterencode(o, 0) ^^^^^^^^^^^^^^^^^ File ".../lib/python3.12/json/encoder.py", line 180, in default raise TypeError(f'Object of type {o.__class__.__name__} ' TypeError: Object of type UUID is not JSON serializable To resolve this issue, you can pass a custom ``JSONEncoder`` using the ``encoder_cls`` parameter. .. code-block:: python import uuid import json from joserfc import jwt, jwk class MyEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, uuid.UUID): return str(o) return super().default(o) key = jwk.OctKey.import_key("secret") claims = {"sub": uuid.uuid4()} jwt.encode({"alg": "HS256"}, claims, key, encoder_cls=MyEncoder) Additionally, ``jwt.decode`` accepts a ``decoder_cls`` parameter. If you need to convert the decoded claims into the appropriate data types, you can provide a custom decoder class. joserfc-1.1.0/docs/guide/registry.rst000066400000000000000000000150101501424510600175660ustar00rootroot00000000000000:description: Advanced usage of registry for JWS, and JWE. .. _registry: Registry ======== .. module:: joserfc :noindex: The ``registry`` is specifically designed to store supported algorithms, allowed algorithms, registered header parameters, and provides methods to validate algorithms and headers. .. note:: We'll use ``JWSRegistry`` as our reference, but keep in mind that the behavior of ``JWERegistry`` is identical. Algorithms ---------- The ``JWSRegistry`` or ``JWERegistry`` serves as a storage for all supported algorithms in JWS or JWE. By default, it enforces the usage of recommended algorithms, ensuring a higher level of security. Find all the supported and recommended algorithms in: - :ref:`jws_algorithms` - :ref:`jwe_algorithms` You have the flexibility to create a custom registry tailored to your specific program requirements, allowing you to define and restrict the algorithms used. For instance, you can design a custom JWS registry that only permits the usage of ``RS256`` and ``ES256`` algorithms. This ensures that only these specific algorithms are allowed in your program. .. code-block:: python from joserfc.jws import JWSRegistry registry = JWSRegistry(algorithms=["RS256", "ES256"]) # jws.serialize_compact(protected, payload, key, registry=registry) An example of a custom JWE registry that only permits the usage of ``{"alg": "A128KW", "enc": "A128GCM"}``: .. code-block:: python from joserfc.jwe import JWERegistry registry = JWERegistry(algorithms=["A128KW", "A128GCM"]) # jwe.encrypt_compact(protected, payload, key, registry=registry) Headers ------- By default, the ``JWSRegistry`` only permits the usage of registered header parameters. Additionally, it verifies the validity of the header parameter values before allowing their usage. Type checking ~~~~~~~~~~~~~ The header parameter registry for JWS and JWE performs an initial check on the value type. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> jws.serialize_compact({"alg": "HS256", "kid": 123}, "hello", key) Traceback (most recent call last): File "", line 1, in File ".../joserfc/jws.py", line 111, in serialize_compact registry.check_header(protected) File ".../joserfc/rfc7515/registry.py", line 68, in check_header validate_registry_header(self.header_registry, header) File ".../joserfc/registry.py", line 194, in validate_registry_header raise InvalidHeaderValueError(f"'{key}' in header {error}") joserfc.errors.InvalidHeaderValueError: invalid_header_value: 'kid' in header must be a str In the above example, ``kid`` MUST be a string instead of an integer. The default registry validates the ``kid`` before processing the serialization. Critical headers ~~~~~~~~~~~~~~~~ There is a special "crit" header parameter for JWS and JWE, which specifies the critical header parameters. These critical parameters are considered mandatory, indicating that they must be present. For example: .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> jws.serialize_compact({"alg": "HS256", "crit": ["kid"]}, "hello", key) Traceback (most recent call last): File "", line 1, in File ".../joserfc/jws.py", line 111, in serialize_compact registry.check_header(protected) File ".../joserfc/rfc7515/registry.py", line 67, in check_header check_crit_header(header) File ".../joserfc/registry.py", line 202, in check_crit_header raise MissingCritHeaderError(k) joserfc.errors.MissingCritHeaderError: missing_crit_header: Missing critical 'kid' value in header Since "kid" is listed as a critical (``crit``) header parameter, it is mandatory and must be included in the header. Additional headers ~~~~~~~~~~~~~~~~~~ By default, the registry for JWS and JWE only permits registered header parameters. Any additional header beyond those supported by the algorithm will result in an error. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> jws.serialize_compact({"alg": "HS256", "custom": "hi"}, "hello", key) Traceback (most recent call last): File "", line 1, in File ".../joserfc/jws.py", line 111, in serialize_compact registry.check_header(protected) File ".../joserfc/rfc7515/registry.py", line 70, in check_header check_supported_header(self.header_registry, header) File ".../joserfc/registry.py", line 183, in check_supported_header raise UnsupportedHeaderError(f"Unsupported {unsupported_keys} in header") joserfc.errors.UnsupportedHeaderError: unsupported_header: Unsupported {'custom'} in header To resolve this error, you have two options. First, you can register the additional header parameters with the registry. This allows the registry to recognize and validate those parameters instead of raising an error. .. code-block:: python from joserfc import jws from joserfc.jws import JWSRegistry from joserfc.registry import HeaderParameter from joserfc.jwk import OctKey key = OctKey.import_key("secret") additional_header_registry = { "custom": HeaderParameter("Custom message", "str", required=True), } registry = JWSRegistry(additional_header_registry) # it will not raise any error jws.serialize_compact({"alg": "HS256", "custom": "hi"}, "hello", key, registry=registry) # this will raise an error, because we "custom" is defined to be required jws.serialize_compact({"alg": "HS256"}, "hello", key, registry=registry) Alternatively, you can choose to disable the strict header checking altogether. By turning off strict header checking, the registry will no longer raise an error for unrecognized header parameters. However, please note that this approach may compromise the security and integrity of the token, so it should be used with caution. .. code-block:: python registry = JWSRegistry(strict_check_header=False) # will not raise any error jws.serialize_compact({"alg": "HS256", "custom": "hi"}, "hello", key, registry=registry) Registry for JWT ---------------- JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`. The ``encode`` and ``decode`` methods accept a ``registry`` parameter. Depending on the algorithm of the JWT, you need to decide whether to use ``JWSRegistry`` or ``JWERegistry``. joserfc-1.1.0/docs/index.rst000066400000000000000000000046011501424510600157340ustar00rootroot00000000000000JOSE RFC ======== ``joserfc`` is a Python library that provides a comprehensive implementation of several essential JSON Object Signing and Encryption (JOSE) standards, including JWS (JSON Web Signature), JWE (JSON Web Encryption), JWK (JSON Web Key), JWA (JSON Web Algorithms), and JWT (JSON Web Tokens). It is derived from Authlib_, but features a redesigned API specific to JOSE functionality. .. _Authlib: https://authlib.org/ Usage ----- A quick and simple JWT encoding and decoding would look something like this: .. code-block:: python >>> from joserfc import jwt >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> encoded = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) >>> encoded 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrIjoidmFsdWUifQ.ni-MJXnZHpFB_8L9P9yllj3RNDfzmD4yBKAyefSctMY' >>> token = jwt.decode(encoded, key) >>> token.header {'alg': 'HS256', 'typ': 'JWT'} >>> token.claims {'k': 'value'} You would find more details and advanced usage in :ref:`jwt` section. .. important:: The string ``"secret"`` employed in the above example is solely intended for demonstration purposes. In a production environment, it is crucial to use a highly secure secret key to ensure robust security measures. RFCs ---- It follows RFCs with extensible API. The module has implementations of: - RFC7515: :ref:`JSON Web Signature ` - RFC7516: :ref:`JSON Web Encryption ` - RFC7517: :ref:`JSON Web Key ` - RFC7518: :ref:`JSON Web Algorithms ` - RFC7519: :ref:`JSON Web Token ` - RFC7520: Examples of Protecting Content Using JSON Object Signing and Encryption - RFC7638: ``thumbprint`` for JWK - RFC8037: :ref:`OKPKey` and ``EdDSA`` algorithm - RFC8812: ``ES256K`` algorithm And draft RFCs implementation of: - :ref:`chacha20` - :ref:`ecdh1pu` .. hint:: RFC7520 is implemented as test cases. Next ---- Explore the following sections to discover more about ``joserfc`` and its features. .. toctree:: :caption: Getting started :hidden: guide/introduction install .. toctree:: :caption: Essentials :hidden: guide/index guide/algorithms guide/registry migrations/index .. toctree:: :caption: Recipes :hidden: recipes/azure recipes/openssl .. toctree:: :caption: Development :hidden: api/index security stability contributing/index changelog joserfc-1.1.0/docs/install.rst000066400000000000000000000031241501424510600162720ustar00rootroot00000000000000:description: Get started with joserfc from installation. Installation ============ .. rst-class:: lead Get started with **joserfc** from installation. ---- We recommend using the latest version of Python. ``joserfc`` supports Python 3.8 and newer. The package has a dependency of cryptography_, if you encountered any issues related with cryptography, you can follow the documentation `installation of cryptography `_. .. _cryptography: https://cryptography.io/ pip install ----------- ``joserfc`` is conveniently available as a Python package on PyPI and can be easily installed using pip. .. tab-set:: .. tab-item:: General .. code-block:: shell pip install joserfc .. tab-item:: C20P and XC20P .. code-block:: shell pip install joserfc pycryptodome .. important:: To use :ref:`chacha20` algorithms, developers have to install the ``PyCryptodome`` module. Dependency management --------------------- There are several ways to manage the dependencies of your project, here are some examples to track ``joserfc`` in your project. pyproject.toml ~~~~~~~~~~~~~~ If you're using ``pyproject.toml`` for your Python project, you can add ``joserfc`` to ``project.dependencies``. .. code-block:: ini :caption: pyproject.toml [project] dependencies = [ "joserfc", ] requirements.txt ~~~~~~~~~~~~~~~~ If you're tracking dependencies in ``requirements.txt``, you can add ``joserfc`` to the requirements file. .. code-block:: text :caption: requirements.txt joserfc joserfc-1.1.0/docs/locales/000077500000000000000000000000001501424510600155145ustar00rootroot00000000000000joserfc-1.1.0/docs/locales/zh/000077500000000000000000000000001501424510600161355ustar00rootroot00000000000000joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/000077500000000000000000000000001501424510600177225ustar00rootroot00000000000000joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/api.po000066400000000000000000001155331501424510600210430ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-24 13:25+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../api/errors.rst:2 msgid "Errors" msgstr "错误类型" #: ../../api/errors.rst:4 msgid "All errors are based on ``joserfc.errors.JoseError``." msgstr "所有的错误类型都是基于``joserfc.errors.JoseError``。" #: joserfc.errors.BadSignatureError:1 of msgid "" "This error is designed for JWS/JWT. It is raised when signature does not " "match." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: ../../docstring joserfc.errors.BadSignatureError.error:1 #: joserfc.errors.ConflictAlgorithmError.error:1 #: joserfc.errors.DecodeError.error:1 joserfc.errors.ExceededSizeError.error:1 #: joserfc.errors.ExpiredTokenError.error:1 #: joserfc.errors.InsecureClaimError.error:1 #: joserfc.errors.InvalidCEKLengthError.error:1 #: joserfc.errors.InvalidClaimError.error:1 #: joserfc.errors.InvalidEncryptedKeyError.error:1 #: joserfc.errors.InvalidEncryptionAlgorithmError.error:1 #: joserfc.errors.InvalidExchangeKeyError.error:1 #: joserfc.errors.InvalidHeaderValueError.error:1 #: joserfc.errors.InvalidKeyIdError.error:1 #: joserfc.errors.InvalidKeyLengthError.error:1 #: joserfc.errors.InvalidKeyTypeError.error:1 #: joserfc.errors.InvalidPayloadError.error:1 #: joserfc.errors.InvalidTokenError.error:1 joserfc.errors.JoseError.error:1 #: joserfc.errors.MissingAlgorithmError.error:1 #: joserfc.errors.MissingClaimError.error:1 #: joserfc.errors.MissingCritHeaderError.error:1 #: joserfc.errors.MissingEncryptionError.error:1 #: joserfc.errors.MissingHeaderError.error:1 #: joserfc.errors.MissingKeyError.error:1 #: joserfc.errors.MissingKeyTypeError.error:1 #: joserfc.errors.UnsupportedAlgorithmError.error:1 #: joserfc.errors.UnsupportedHeaderError.error:1 #: joserfc.errors.UnsupportedKeyAlgorithmError.error:1 #: joserfc.errors.UnsupportedKeyOperationError.error:1 #: joserfc.errors.UnsupportedKeyUseError.error:1 of msgid "short-string error code" msgstr "短字符串错误代码" #: joserfc.errors.DecodeError:1 of #, fuzzy msgid "" "This error is designed for JWS/JWE. It is raised when deserialization and" " decryption fails." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.ExceededSizeError:1 of msgid "" "This error is designed for DEF zip algorithm. It raised when the " "compressed data exceeds the maximum allowed length." msgstr "该错误是为 DEF 压缩算法设计的,当压缩数据超过允许的最大长度时触发。" #: joserfc.errors.ExpiredTokenError:1 of #, fuzzy msgid "This error is designed for JWT. It raised when the token is expired." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: ../../docstring joserfc.errors.ExpiredTokenError.description:1 #: joserfc.errors.InvalidCEKLengthError.description:1 #: joserfc.errors.InvalidEncryptedKeyError.description:1 #: joserfc.errors.InvalidExchangeKeyError.description:1 #: joserfc.errors.InvalidTokenError.description:1 #: joserfc.errors.JoseError.description:1 #: joserfc.errors.MissingAlgorithmError.description:1 #: joserfc.errors.MissingEncryptionError.description:1 of msgid "long-string to describe this error" msgstr "描述此错误的长字符串" #: joserfc.errors.InsecureClaimError:1 of #, fuzzy msgid "" "This error is designed for JWT. It raised when the claim contains " "sensitive information." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.InvalidClaimError:1 of #, fuzzy msgid "" "This error is designed for JWT. It raised when the claim contains invalid" " values or types." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.InvalidEncryptionAlgorithmError:1 of msgid "" "This error is designed for JWE. It is raised when \"enc\" value does not " "work together with \"alg\" value." msgstr "该错误是为 JWE 设计的,当 \"enc\" 值与 \"alg\" 值不兼容时触发。" #: joserfc.errors.InvalidPayloadError:1 of #, fuzzy msgid "" "This error is designed for JWT. It raised when the payload is not a valid" " JSON object." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.InvalidTokenError:1 of #, fuzzy msgid "This error is designed for JWT. It raised when the token is not valid yet." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.JoseError:1 of msgid "Base Exception for all errors in joserfc." msgstr "joserfc 中所有错误的基类异常。" #: joserfc.errors.MissingClaimError:1 of #, fuzzy msgid "" "This error is designed for JWT. It raised when the required claims are " "missing." msgstr "该错误是为 JWS/JWT 设计的,当签名不匹配时触发。" #: joserfc.errors.MissingCritHeaderError:1 of msgid "This error happens when the critical header does not exist." msgstr "" #: joserfc.errors.MissingEncryptionError:1 of #, fuzzy msgid "" "This error is designed for JWE. It is raised when the 'enc' value in " "header is missing." msgstr "该错误是为 JWE 设计的,当 \"enc\" 值与 \"alg\" 值不兼容时触发。" #: joserfc.errors.MissingHeaderError:1 of msgid "This error happens when the required header does not exist." msgstr "" #: ../../api/index.rst:2 msgid "API References" msgstr "API 参考" #: ../../api/index.rst:4 msgid "Here covers the interfaces of JWS, JWE, JWK, and JWT." msgstr "此处涵盖 JWS、JWE、JWK 和 JWT 的接口。" #: ../../api/index.rst:10 ../../api/jws.rst:4 msgid "JWS API" msgstr "JWS API" #: ../../api/index.rst:14 msgid "Most :ref:`jwt` are encoded with JWS in compact serialization." msgstr "大多数 :ref:`jwt` 使用 JWS 进行紧凑序列化编码。" #: ../../api/index.rst:16 ../../api/jwe.rst:4 msgid "JWE API" msgstr "JWE API" #: ../../api/index.rst:20 msgid "" "JSON Web Encryption (JWE) represents encrypted content using JSON-based " "data structures." msgstr "JSON Web Encryption (JWE) 使用基于 JSON 的数据结构表示加密内容。" #: ../../api/index.rst:22 ../../api/jwk.rst:4 msgid "JWK API" msgstr "JWK API" #: ../../api/index.rst:26 msgid "" "Learn how to use ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and JSON " "Web Key Set." msgstr "了解如何使用 ``OctKey``、``RSAKey``、``ECKey``、``OKPKey`` 和 JSON Web 密钥集。" #: ../../api/index.rst:28 ../../api/jwt.rst:4 msgid "JWT API" msgstr "JWT API" #: ../../api/index.rst:32 msgid "JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`." msgstr "JSON Web Token (JWT) 构建于 :ref:`jws` 或 :ref:`jwe` 之上。" #: ../../api/jwe.rst:6 msgid "" "This part of the documentation covers all the interfaces of " "``joserfc.jwe``." msgstr "文档的这一部分涵盖了 ``joserfc.jwe`` 的所有接口。" #: joserfc.rfc7516.models.CompactEncryption:1 of msgid "" "An object to represent the JWE Compact Serialization. It is usually " "returned by ``decrypt_compact`` method." msgstr "一个对象,用于表示 JWE 紧凑序列化。通常由 ``decrypt_compact`` 方法返回。" #: joserfc.rfc7516.models.CompactEncryption.attach_recipient:1 of msgid "" "Add a recipient to the JWE Compact Serialization. Please add a key that " "comply with the given \"alg\" value." msgstr "向 JWE 紧凑序列化添加一个接收者。请添加符合给定 \"alg\" 值的密钥。" #: ../../api/jwe.rst ../../api/jwk.rst ../../api/jws.rst ../../api/jwt.rst msgid "Parameters" msgstr "参数" #: joserfc.rfc7516.models.CompactEncryption.attach_recipient:4 #: joserfc.rfc7516.models.FlattenedJSONEncryption.add_recipient:5 #: joserfc.rfc7516.models.GeneralJSONEncryption.add_recipient:5 of msgid "an instance of a key, e.g. (OctKey, RSAKey, ECKey, and etc)" msgstr "一个密钥实例,例如 (OctKey, RSAKey, ECKey 等)" #: joserfc.rfc7516.models.CompactEncryption.attach_recipient:5 of msgid "extra header in dict" msgstr "字典中的额外 header" #: ../../docstring joserfc.jwe.CompactEncryption.plaintext:1 of msgid "the plaintext in bytes" msgstr "字节形式的明文" #: ../../docstring joserfc.jwe.CompactEncryption.protected:1 of msgid "protected header in dict" msgstr "字典中的受保护 header" #: joserfc.rfc7516.models.FlattenedJSONEncryption:1 of msgid "" "An object to represent the JWE Flattened JSON Serialization. It is used " "by ``encrypt_json``, and it is usually returned by ``decrypt_json`` " "method." msgstr "一个对象,用于表示 JWE 扁平 JSON 序列化。由 ``encrypt_json`` 使用,通常由 ``decrypt_json`` 方法返回。" #: joserfc.rfc7516.models.FlattenedJSONEncryption:4 of msgid "To construct an object of ``FlattenedJSONEncryption``:" msgstr "构造 ``FlattenedJSONEncryption`` 对象:" #: joserfc.rfc7516.models.FlattenedJSONEncryption.add_recipient:1 #: joserfc.rfc7516.models.GeneralJSONEncryption.add_recipient:1 of msgid "" "Add a recipient to the JWE JSON Serialization. Please add a key that " "comply with the \"alg\" to this recipient." msgstr "向 JWE JSON 序列化添加一个接收者。请添加符合 \"alg\" 的密钥。" #: joserfc.rfc7516.models.FlattenedJSONEncryption.add_recipient:4 #: joserfc.rfc7516.models.GeneralJSONEncryption.add_recipient:4 of msgid "recipient's own (unprotected) header" msgstr "接收者自己的(未保护的)header" #: ../../docstring joserfc.jwe.FlattenedJSONEncryption.flattened:1 #: joserfc.jwe.GeneralJSONEncryption.flattened:1 of msgid "represents if the object is in flatten syntax" msgstr "表示对象是否为扁平语法" #: joserfc.rfc7516.models.GeneralJSONEncryption:1 of msgid "" "An object to represent the JWE General JSON Serialization. It is used by " "``encrypt_json``, and it is usually returned by ``decrypt_json`` method." msgstr "一个对象,用于表示 JWE 通用 JSON 序列化。由 ``encrypt_json`` 使用,通常由 ``decrypt_json`` 方法返回。" #: joserfc.rfc7516.models.GeneralJSONEncryption:4 of msgid "To construct an object of ``GeneralJSONEncryption``:" msgstr "构造 ``GeneralJSONEncryption`` 对象:" #: joserfc.rfc7516.registry.JWERegistry:1 of msgid "" "A registry for JSON Web Encryption to keep all the supported algorithms. " "An instance of ``JWERegistry`` is usually used together with methods in " "``joserfc.jwe``." msgstr "一个用于 JSON Web Encryption 的注册表,用于保存所有支持的算法。通常与 ``joserfc.jwe`` 中的方法一起使用。" #: joserfc.rfc7515.registry.JWSRegistry:5 #: joserfc.rfc7516.registry.JWERegistry:5 of msgid "extra header parameters registry" msgstr "额外的 header 参数注册表" #: joserfc.rfc7515.registry.JWSRegistry:6 #: joserfc.rfc7516.registry.JWERegistry:6 of msgid "allowed algorithms to be used" msgstr "允许使用的算法" #: joserfc.rfc7516.registry.JWERegistry:7 of msgid "validating all recipients in a JSON serialization" msgstr "验证 JSON 序列化中的所有接收者" #: joserfc.rfc7515.registry.JWSRegistry:7 #: joserfc.rfc7516.registry.JWERegistry:8 of msgid "only allow header key in the registry to be used" msgstr "仅允许使用注册表中的 header 密钥" #: joserfc.rfc7515.registry.JWSRegistry.check_header:1 #: joserfc.rfc7516.registry.JWERegistry.check_header:1 of msgid "Check and validate the fields in header part of a JWS object." msgstr "检查并验证 JWS 对象 header 部分的字段。" #: joserfc.rfc7516.registry.JWERegistry.get_alg:1 of msgid "Get the allowed (\"alg\") algorithm instance of the given name." msgstr "获取给定名称的允许 (\"alg\") 算法实例。" #: joserfc.rfc7516.registry.JWERegistry.get_alg:3 of msgid "value of the ``alg``, e.g. ``ECDH-ES``, ``A128KW``" msgstr "``alg`` 的值,例如 ``ECDH-ES``、``A128KW``" #: joserfc.rfc7516.registry.JWERegistry.get_enc:1 of msgid "Get the allowed (\"enc\") algorithm instance of the given name." msgstr "获取给定名称的允许 (\"enc\") 算法实例。" #: joserfc.rfc7516.registry.JWERegistry.get_enc:3 of msgid "value of the ``enc``, e.g. ``A128CBC-HS256``, ``A128GCM``" msgstr "``enc`` 的值,例如 ``A128CBC-HS256``、``A128GCM``" #: joserfc.rfc7516.registry.JWERegistry.get_zip:1 of msgid "Get the allowed (\"zip\") algorithm instance of the given name." msgstr "获取给定名称的允许 (\"zip\") 算法实例。" #: joserfc.rfc7516.registry.JWERegistry.get_zip:3 of msgid "value of the ``zip``, e.g. ``DEF``" msgstr "``zip`` 的值,例如 ``DEF``" #: joserfc.jwe.decrypt_compact:1 of msgid "" "Extract and validate the JWE Compact Serialization (in string, or bytes) " "with the given key. An JWE Compact Serialization looks like:" msgstr "使用给定密钥提取并验证 JWE 紧凑序列化(字符串或字节)。JWE 紧凑序列化如下所示:" #: joserfc.jwe.decrypt_compact:4 joserfc.jws.deserialize_compact:4 of msgid "line breaks for display purposes only" msgstr "仅用于显示目的的换行" #: joserfc.jwe.decrypt_compact:14 of msgid "a string (or bytes) of the JWE Compact Serialization" msgstr "JWE 紧凑序列化的字符串(或字节)" #: joserfc.jwe.decrypt_compact:15 of msgid "a flexible private key to decrypt the serialization" msgstr "用于解密序列化的灵活型私钥" #: joserfc.jwe.decrypt_compact:16 joserfc.jwe.decrypt_json:6 #: joserfc.jwe.encrypt_compact:13 joserfc.jwe.encrypt_json:22 #: joserfc.jws.deserialize_compact:16 joserfc.jws.deserialize_json:5 #: joserfc.jws.serialize_compact:14 joserfc.jws.validate_compact:6 #: joserfc.jwt.decode:6 joserfc.jwt.encode:6 of msgid "a list of allowed algorithms" msgstr "允许的算法列表" #: joserfc.jwe.decrypt_compact:17 joserfc.jwe.decrypt_json:7 #: joserfc.jwe.encrypt_compact:14 joserfc.jwe.encrypt_json:23 of msgid "a JWERegistry to use" msgstr "要使用的 JWERegistry" #: joserfc.jwe.decrypt_compact:18 joserfc.jwe.decrypt_json:8 #: joserfc.jwe.encrypt_compact:15 joserfc.jwe.encrypt_json:24 of msgid "only required when using ECDH-1PU" msgstr "仅在使用 ECDH-1PU 时需要" #: ../../api/jwe.rst ../../api/jwk.rst ../../api/jws.rst msgid "Returns" msgstr "返回值" #: joserfc.jwe.decrypt_compact:19 of msgid "object of the ``CompactEncryption``" msgstr "``CompactEncryption`` 对象" #: joserfc.jwe.decrypt_json:1 of msgid "" "Decrypt the JWE JSON Serialization (in dict) to a " "``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object." msgstr "" "解密 JWE JSON 序列化(字典形式)为 ``GeneralJSONEncryption`` 或 " "``FlattenedJSONEncryption`` 对象。" #: joserfc.jwe.decrypt_json:4 joserfc.jwe.encrypt_json:25 of msgid "JWE JSON Serialization in dict" msgstr "字典类型的 JWE JSON 序列化" #: joserfc.jwe.decrypt_json:5 of msgid "a flexible private key to decrypt the CEK" msgstr "用于解密 CEK 的灵活型私钥" #: joserfc.jwe.decrypt_json:9 joserfc.jwe.encrypt_json:20 of msgid "an instance of ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption``" msgstr "``GeneralJSONEncryption`` 或 ``FlattenedJSONEncryption`` 的实例" #: joserfc.jwe.encrypt_compact:1 of msgid "" "Generate a JWE Compact Serialization. The JWE Compact Serialization " "represents encrypted content as a compact, URL-safe string. This string " "is::" msgstr "生成 JWE 紧凑序列化。JWE 紧凑序列化将加密内容表示为紧凑的 URL 安全字符串。该字符串如下所示:" #: joserfc.jwe.encrypt_compact:10 of msgid "protected header part of the JWE, in dict" msgstr "JWE 的受保护 header 部分,字典形式" #: joserfc.jwe.encrypt_compact:11 of msgid "the content (message) to be encrypted" msgstr "要加密的内容(消息)" #: joserfc.jwe.encrypt_compact:12 joserfc.jwe.encrypt_json:21 of msgid "a public key used to encrypt the CEK" msgstr "用于加密 CEK 的公钥" #: joserfc.jwe.encrypt_compact:16 of msgid "JWE Compact Serialization in bytes" msgstr "JWE 紧凑序列化的字节形式" #: joserfc.jwe.encrypt_json:1 of msgid "" "Generate a JWE JSON Serialization (in dict). The JWE JSON Serialization " "represents encrypted content as a JSON object. This representation is " "neither optimized for compactness nor URL safe." msgstr "生成 JWE JSON 序列化(字典形式)。JWE JSON 序列化将加密内容表示为 JSON 对象。此表示既不优化紧凑性,也不 URL 安全。" #: joserfc.jwe.encrypt_json:5 of msgid "" "When calling this method, developers MUST construct an instance of a " "``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object. Here is " "an example::" msgstr "" "调用此方法时,开发人员必须构造 ``GeneralJSONEncryption`` 或 ``FlattenedJSONEncryption`` " "对象的实例。以下是一个示例:" #: ../../api/jwk.rst:6 msgid "" "This part of the documentation covers all the interfaces of " "``joserfc.jwk``." msgstr "文档的这一部分涵盖了 ``joserfc.jwk`` 的所有接口。" #: joserfc.rfc7518.ec_key.ECKey.generate_key:1 of msgid "Generate a ``ECKey`` with the given \"crv\" value." msgstr "生成具有给定 \"crv\" 值的 ``ECKey``。" #: joserfc.rfc7518.ec_key.ECKey.generate_key:3 of msgid "ECKey curve name" msgstr "ECKey 曲线名称" #: joserfc.rfc7518.ec_key.ECKey.generate_key:4 #: joserfc.rfc7518.oct_key.OctKey.generate_key:4 #: joserfc.rfc7518.rsa_key.RSAKey.generate_key:4 #: joserfc.rfc8037.okp_key.OKPKey.generate_key:4 of msgid "extra parameter in JWK" msgstr "JWK 中的额外参数" #: joserfc.rfc7518.ec_key.ECKey.generate_key:5 #: joserfc.rfc7518.rsa_key.RSAKey.generate_key:5 #: joserfc.rfc8037.okp_key.OKPKey.generate_key:5 of msgid "generate a private key or public key" msgstr "生成私钥或公钥" #: joserfc.rfc7518.ec_key.ECKey.generate_key:6 #: joserfc.rfc7518.oct_key.OctKey.generate_key:6 #: joserfc.rfc7518.rsa_key.RSAKey.generate_key:6 #: joserfc.rfc8037.okp_key.OKPKey.generate_key:6 of msgid "add ``kid`` automatically" msgstr "自动添加 ``kid``" #: ../../docstring joserfc.jwk.ECKey.value_registry:1 of msgid "" "Registry definition for EC Key https://www.rfc-" "editor.org/rfc/rfc7518#section-6.2" msgstr "EC 密钥注册表定义 https://www.rfc-editor.org/rfc/rfc7518#section-6.2" #: joserfc._keys.JWKRegistry:1 of msgid "" "A registry for JWK to record ``joserfc`` supported key types. Normally, " "you would use explicit key types like ``OctKey``, ``RSAKey``; This " "registry provides a way to dynamically import and generate keys. For " "instance:" msgstr "" "JWK 的注册表,用于记录 ``joserfc`` 支持的密钥类型。通常,您会使用显式密钥类型,如 " "``OctKey``、``RSAKey``;此注册表提供了一种动态导入和生成密钥的方法。例如:" #: joserfc._keys.JWKRegistry.generate_key:1 of msgid "" "A class method for generating key according to the given key type. When " "``key_type`` is \"oct\" and \"RSA\", the second parameter SHOULD be a key" " size in bits. When ``key_type`` is \"EC\" and \"OKP\", the second " "parameter SHOULD be a \"crv\" string." msgstr "" "根据给定的密钥类型生成密钥的类方法。当 ``key_type`` 为 \"oct\" 和 \"RSA\" 时,第二个参数应为位大小。当 " "``key_type`` 为 \"EC\" 和 \"OKP\" 时,第二个参数应为 \"crv\" 字符串。" #: joserfc._keys.JWKRegistry.import_key:1 of msgid "" "A class method for importing a key from bytes, string, and dict. When " "``value`` is a dict, this method can tell the key type automatically, " "otherwise, developers SHOULD pass the ``key_type`` themselves." msgstr "" "从字节、字符串和字典导入密钥的类方法。当 ``value`` 为字典时,此方法可以自动识别密钥类型,否则,开发人员应自行传递 " "``key_type``。" #: joserfc._keys.JWKRegistry.import_key:5 joserfc.jwk.import_key:5 of msgid "the key data in bytes, string, or dict." msgstr "字节、字符串或字典形式的密钥数据。" #: joserfc._keys.JWKRegistry.import_key:6 joserfc.jwk.import_key:6 of msgid "an optional key type in string." msgstr "可选的密钥类型字符串。" #: joserfc._keys.JWKRegistry.import_key:7 joserfc.jwk.import_key:7 of msgid "extra key parameters" msgstr "额外的密钥参数" #: joserfc._keys.JWKRegistry.import_key:8 joserfc.jwk.import_key:8 of msgid "OctKey, RSAKey, ECKey, or OKPKey" msgstr "OctKey、RSAKey、ECKey 或 OKPKey" #: joserfc.rfc8037.okp_key.OKPKey:1 of msgid "Key class of the ``OKP`` key type." msgstr "``OKP`` 密钥类型的密钥类。" #: joserfc.rfc8037.okp_key.OKPKey.generate_key:1 of msgid "Generate a ``OKPKey`` with the given \"crv\" value." msgstr "生成具有给定 \"crv\" 值的 ``OKPKey``。" #: joserfc.rfc8037.okp_key.OKPKey.generate_key:3 of msgid "OKPKey curve name" msgstr "OKPKey 曲线名称" #: ../../docstring joserfc.jwk.OKPKey.value_registry:1 of msgid "" "Registry definition for OKP Key https://www.rfc-" "editor.org/rfc/rfc8037#section-2" msgstr "OKP 密钥注册表定义 https://www.rfc-editor.org/rfc/rfc8037#section-2" #: joserfc.rfc7518.oct_key.OctKey:1 of msgid "OctKey is a symmetric key, defined by RFC7518 Section 6.4." msgstr "OctKey 是对称密钥,由 RFC7518 第 6.4 节定义。" #: joserfc.rfc7518.oct_key.OctKey.generate_key:1 of msgid "Generate a ``OctKey`` with the given bit size (not bytes)." msgstr "生成具有给定位大小(不是字节)的 ``OctKey``。" #: joserfc.rfc7518.oct_key.OctKey.generate_key:3 #: joserfc.rfc7518.rsa_key.RSAKey.generate_key:3 of msgid "size in bit" msgstr "位大小" #: joserfc.rfc7518.oct_key.OctKey.generate_key:5 of msgid "must be True" msgstr "必须为 True" #: ../../docstring joserfc.jwk.OctKey.value_registry:1 of msgid "https://www.rfc-editor.org/rfc/rfc7518#section-6.4" msgstr "https://www.rfc-editor.org/rfc/rfc7518#section-6.4" #: joserfc.rfc7518.rsa_key.RSAKey.generate_key:1 of msgid "Generate a ``RSAKey`` with the given bit size (not bytes)." msgstr "生成具有给定位大小(不是字节)的 ``RSAKey``。" #: ../../docstring joserfc.jwk.RSAKey.value_registry:1 of msgid "" "Registry definition for RSA Key https://www.rfc-" "editor.org/rfc/rfc7518#section-6.3" msgstr "RSA 密钥注册表定义 https://www.rfc-editor.org/rfc/rfc7518#section-6.3" #: joserfc.jwk.generate_key:1 of #, fuzzy msgid "" "Generating key according to the given key type. When ``key_type`` is " "\"oct\" and \"RSA\", the second parameter SHOULD be a key size in bits. " "When ``key_type`` is \"EC\" and \"OKP\", the second parameter SHOULD be a" " \"crv\" string." msgstr "" "根据给定的密钥类型生成密钥的类方法。当 ``key_type`` 为 \"oct\" 和 \"RSA\" 时,第二个参数应为位大小。当 " "``key_type`` 为 \"EC\" 和 \"OKP\" 时,第二个参数应为 \"crv\" 字符串。" #: joserfc.jwk.guess_key:1 of msgid "Guess key from a various sources." msgstr "从各种来源猜测密钥。" #: joserfc.jwk.guess_key:3 of msgid "a very flexible key" msgstr "非常灵活的密钥" #: joserfc.jwk.guess_key:4 of msgid "a protocol that has ``headers`` and ``set_kid`` methods" msgstr "具有 ``headers`` 和 ``set_kid`` 方法的协议" #: joserfc.jwk.guess_key:5 of msgid "pick a random key from key set" msgstr "从密钥集中随机选择一个密钥" #: joserfc.jwk.import_key:1 of #, fuzzy msgid "" "Importing a key from bytes, string, and dict. When ``value`` is a dict, " "this method can tell the key type automatically, otherwise, developers " "SHOULD pass the ``key_type`` themselves." msgstr "" "从字节、字符串和字典导入密钥的类方法。当 ``value`` 为字典时,此方法可以自动识别密钥类型,否则,开发人员应自行传递 " "``key_type``。" #: ../../api/jws.rst:6 msgid "" "This part of the documentation covers all the interfaces of " "``joserfc.jws``." msgstr "文档的这一部分涵盖了 ``joserfc.jws`` 的所有接口。" #: joserfc.rfc7515.model.CompactSignature:1 of msgid "" "JSON Web Signature object for compact mode. This object is used to " "represent the JWS instance." msgstr "用于紧凑模式的 JSON Web Signature 对象。此对象用于表示 JWS 实例。" #: joserfc.rfc7515.model.FlattenedJSONSignature:1 of msgid "JSON Signature object that represents a flattened JSON serialization." msgstr "表示扁平 JSON 序列化的 JSON Signature 对象。" #: ../../docstring joserfc.jws.FlattenedJSONSignature.flattened:1 of msgid "mark it as flattened" msgstr "标记为扁平" #: ../../docstring joserfc.jws.FlattenedJSONSignature.member:1 of msgid "the only header member" msgstr "唯一的 header 成员" #: ../../docstring joserfc.jws.FlattenedJSONSignature.payload:1 #: joserfc.jws.GeneralJSONSignature.payload:1 of msgid "payload content" msgstr "有效载荷内容" #: joserfc.rfc7515.model.GeneralJSONSignature:1 of msgid "JSON Signature object that represents a general JSON serialization." msgstr "表示通用 JSON 序列化的 JSON Signature 对象。" #: ../../docstring joserfc.jws.GeneralJSONSignature.flattened:1 of msgid "mark it as not flattened (general)" msgstr "标记为非扁平(通用)" #: ../../docstring joserfc.jws.GeneralJSONSignature.members:1 of msgid "a list of header members" msgstr "header 成员列表" #: joserfc.rfc7515.model.HeaderMember:1 of msgid "" "A header member of the JSON signature. It is combined with protected " "header, and unprotected header." msgstr "JSON 签名的 header 成员。它由受保护和未保护的 header 组成。" #: ../../docstring joserfc.jws.HeaderMember.header:1 of msgid "unprotected header" msgstr "未保护的 header" #: ../../docstring joserfc.jws.HeaderMember.protected:1 of msgid "protected header" msgstr "受保护的 header" #: joserfc.rfc7515.model.JWSAlgModel:1 of msgid "" "Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement" " the algorithms for JWS with this base implementation." msgstr "JWS 算法接口。JWA 规范 (RFC7518) 应使用此基础实现来实现 JWS 的算法。" #: joserfc.rfc7515.model.JWSAlgModel.sign:1 of msgid "Sign the text msg with a private/sign key." msgstr "使用私钥/签名密钥签署文本消息。" #: joserfc.rfc7515.model.JWSAlgModel.sign:3 #: joserfc.rfc7515.model.JWSAlgModel.verify:3 of msgid "message bytes to be signed" msgstr "要签名的消息字节" #: joserfc.rfc7515.model.JWSAlgModel.sign:4 of msgid "private key to sign the message" msgstr "用于签名消息的私钥" #: joserfc.rfc7515.model.JWSAlgModel.sign:5 of msgid "bytes" msgstr "字节" #: joserfc.rfc7515.model.JWSAlgModel.verify:1 of msgid "Verify the signature of text msg with a public/verify key." msgstr "使用公钥/验证密钥验证文本消息的签名。" #: joserfc.rfc7515.model.JWSAlgModel.verify:4 of msgid "result signature to be compared" msgstr "要比较的结果签名" #: joserfc.rfc7515.model.JWSAlgModel.verify:5 of msgid "public key to verify the signature" msgstr "用于验证签名的公钥" #: joserfc.rfc7515.model.JWSAlgModel.verify:6 of msgid "boolean" msgstr "布尔值" #: joserfc.rfc7515.registry.JWSRegistry:1 of msgid "" "A registry for JSON Web Signature to keep all the supported algorithms. " "An instance of ``JWSRegistry`` is usually used together with methods in " "``joserfc.jws``." msgstr "用于 JSON Web Signature 的注册表,用于保存所有支持的算法。通常与 ``joserfc.jws`` 中的方法一起使用。" #: joserfc.rfc7515.registry.JWSRegistry.get_alg:1 of msgid "Get the allowed algorithm instance of the given name." msgstr "获取给定名称的允许算法实例。" #: joserfc.rfc7515.registry.JWSRegistry.get_alg:3 of msgid "value of the ``alg``, e.g. ``HS256``, ``RS256``" msgstr "``alg`` 的值,例如 ``HS256``、``RS256``" #: joserfc.rfc7515.registry.JWSRegistry.register:1 of msgid "Register a given JWS algorithm instance to the registry." msgstr "将给定的 JWS 算法实例注册到注册表。" #: joserfc.jws.deserialize_compact:1 of msgid "" "Extract and validate the JWS Compact Serialization (in string, or bytes) " "with the given key. An JWE Compact Serialization looks like:" msgstr "使用给定密钥提取并验证 JWS 紧凑序列化(字符串或字节)。JWE 紧凑序列化如下所示:" #: joserfc.jws.deserialize_compact:14 of msgid "a string (or bytes) of the JWS Compact Serialization" msgstr "JWS 紧凑序列化的字符串(或字节)" #: joserfc.jws.deserialize_compact:15 joserfc.jws.deserialize_json:4 #: joserfc.jws.validate_compact:5 of msgid "a flexible public key to verify the signature" msgstr "用于验证签名的灵活公钥" #: joserfc.jws.deserialize_compact:17 joserfc.jws.deserialize_json:6 #: joserfc.jws.serialize_compact:15 joserfc.jws.validate_compact:7 of msgid "a JWSRegistry to use" msgstr "要使用的 JWSRegistry" #: joserfc.jws.deserialize_compact:18 of msgid "object of the ``CompactSignature``" msgstr "``CompactSignature`` 对象" #: joserfc.jws.deserialize_json:1 of msgid "Extract and validate the JWS (in string) with the given key." msgstr "使用给定密钥提取并验证 JWS(字符串形式)。" #: joserfc.jws.deserialize_json:3 of msgid "a dict of the JSON signature" msgstr "字典形式的 JSON Signature" #: joserfc.jws.deserialize_json:7 of msgid "object of the SignatureData" msgstr "SignatureData 对象" #: joserfc.jws.deserialize_json joserfc.jwt.decode #: joserfc.rfc7515.compact.extract_compact of msgid "raise" msgstr "触发" #: joserfc.jws.deserialize_json:8 of msgid "ValueError or BadSignatureError" msgstr "ValueError 或 BadSignatureError" #: joserfc.jws.detach_content:1 of msgid "" "In some contexts, it is useful to integrity-protect content that is not " "itself contained in a JWS. This method is an implementation of " "https://www.rfc-editor.org/rfc/rfc7515#appendix-F" msgstr "" "在某些情况下,保护不包含在 JWS 中的内容的完整性是有用的。此方法是 https://www.rfc-" "editor.org/rfc/rfc7515#appendix-F 的实现" #: joserfc.jws.detach_content:5 of msgid "It is used to detach the content of the compact and JSON serialization." msgstr "用于分离紧凑和 JSON 序列化内容。" #: joserfc.jws.detach_content:16 of msgid "You can also detach the JSON serialization:" msgstr "您还可以分离 JSON 序列化:" #: joserfc.rfc7515.compact.extract_compact:1 of msgid "Extract the JWS Compact Serialization from bytes to object." msgstr "从字节中提取 JWS 紧凑序列化为对象。" #: joserfc.rfc7515.compact.extract_compact:3 of msgid "JWS in bytes" msgstr "字节形式的 JWS" #: joserfc.rfc7515.compact.extract_compact:4 of msgid "DecodeError" msgstr "DecodeError" #: joserfc.jws.serialize_compact:1 of msgid "" "Generate a JWS Compact Serialization. The JWS Compact Serialization " "represents digitally signed or MACed content as a compact, URL-safe " "string, per Section 7.1." msgstr "生成 JWS 紧凑序列化。JWS 紧凑序列化将数字签名或 MAC 内容表示为紧凑的 URL 安全字符串,参见第 7.1 节。" #: joserfc.jws.serialize_compact:11 of msgid "protected header part of the JWS, in dict" msgstr "JWS 的受保护 header 部分,字典形式" #: joserfc.jws.serialize_compact:12 of msgid "payload data of the JWS, in bytes" msgstr "JWS 的有效载荷数据,字节形式" #: joserfc.jws.serialize_compact:13 of msgid "a flexible private key to sign the signature" msgstr "用于签名的灵活私钥" #: joserfc.jws.serialize_compact:16 of msgid "JWS in str" msgstr "字符串形式的 JWS" #: joserfc.jws.serialize_json:1 of msgid "" "Generate a JWS JSON Serialization (in dict). The JWS JSON Serialization " "represents digitally signed or MACed content as a JSON object. This " "representation is neither optimized for compactness nor URL-safe." msgstr "" "生成 JWS JSON 序列化(字典形式)。JWS JSON 序列化将数字签名或 MAC 内容表示为 JSON 对象。此表示既不优化紧凑性,也不 " "URL 安全。" #: joserfc.jws.serialize_json:5 of msgid "A general JWS JSON Serialization contains:" msgstr "通用 JWS JSON 序列化包含:" #: joserfc.jws.serialize_json:7 of msgid "payload" msgstr "有效载荷" #: joserfc.jws.serialize_json:8 of msgid "" "The \"payload\" member MUST be present and contain the value " "BASE64URL(JWS Payload)." msgstr "“有效载荷”成员必须存在,并包含值 BASE64URL(JWS Payload)。" #: joserfc.jws.serialize_json:11 of msgid "signatures" msgstr "签名" #: joserfc.jws.serialize_json:12 of msgid "" "The \"signatures\" member value MUST be an array of JSON objects. Each " "object represents a signature or MAC over the JWS Payload and the JWS " "Protected Header." msgstr "“签名”成员值必须是 JSON 对象数组。每个对象表示 JWS 有效载荷和 JWS 受保护 header 的签名或 MAC。" #: joserfc.jws.serialize_json:16 of msgid "A flatten JWS JSON Serialization looks like:" msgstr "扁平 JWS JSON 序列化如下所示:" #: joserfc.jws.validate_compact:1 of msgid "" "Validate the JWS Compact Serialization with the given key. This method is" " usually used together with ``extract_compact``." msgstr "使用给定密钥验证 JWS 紧凑序列化。此方法通常与 ``extract_compact`` 一起使用。" #: joserfc.jws.validate_compact:4 of msgid "object of the JWS Compact Serialization" msgstr "JWS 紧凑序列化的对象" #: ../../api/jwt.rst:6 msgid "" "This part of the documentation covers all the interfaces of " "``joserfc.jwt``." msgstr "文档的这一部分涵盖了 ``joserfc.jwt`` 的所有接口。" #: joserfc.rfc7519.registry.JWTClaimsRegistry.validate_aud:1 of msgid "" "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." msgstr "" "“aud”(受众)声明标识 JWT 预期的接收者。每个预期处理 JWT " "的主体必须在受众声明中标识自己。如果处理声明的主体在存在此声明时未在“aud”声明中标识自己,则必须拒绝 JWT。一般情况下,“aud”值是包含 " "StringOrURI 值的区分大小写字符串数组。在 JWT 只有一个受众的特殊情况下,“aud”值可以是包含 StringOrURI " "值的单个区分大小写字符串。受众值的解释通常是特定于应用程序的。使用此声明是可选的。" #: joserfc.rfc7519.registry.JWTClaimsRegistry.validate_exp:1 of msgid "" "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." msgstr "" "“exp”(到期时间)声明标识 JWT " "必须在或之后的到期时间。处理“exp”声明要求当前日期/时间必须早于“exp”声明中列出的到期日期/时间。实现者可以提供一些小的余地,通常不超过几分钟,以考虑时钟偏差。其值必须是包含" " NumericDate 值的数字。使用此声明是可选的。" #: joserfc.rfc7519.registry.JWTClaimsRegistry.validate_iat:1 of msgid "" "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." msgstr "" "“iat”(签发时间)声明标识 JWT 的签发时间。此声明可用于确定 JWT 的年龄。其值必须是包含 NumericDate " "值的数字。使用此声明是可选的。" #: joserfc.rfc7519.registry.JWTClaimsRegistry.validate_nbf:1 of msgid "" "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." msgstr "" "“nbf”(不早于)声明标识 JWT " "必须在之前的时间。处理“nbf”声明要求当前日期/时间必须晚于或等于“nbf”声明中列出的不早于日期/时间。实现者可以提供一些小的余地,通常不超过几分钟,以考虑时钟偏差。其值必须是包含" " NumericDate 值的数字。使用此声明是可选的。" #: joserfc.jwt.Token:1 of msgid "The extracted token object, which contains ``header`` and ``claims``." msgstr "提取的 Token 对象,包含 ``header`` 和 ``claims``。" #: joserfc.jwt.Token:3 of msgid "the header part of the JWT" msgstr "JWT 的 header 部分" #: joserfc.jwt.Token:4 of msgid "the payload part of the JWT" msgstr "JWT 的有效载荷 (payload) 部分" #: ../../docstring joserfc.jwt.Token.claims:1 of msgid "payload claims in dict" msgstr "字典形式的有效载荷 (payload) 声明 (claims)" #: ../../docstring joserfc.jwt.Token.header:1 of msgid "header in dict" msgstr "字典形式的 header" #: joserfc.rfc7519.claims.check_sensitive_data:1 of msgid "Check if claims contains sensitive information." msgstr "检查声明是否包含敏感信息。" #: joserfc.jwt.decode:1 of msgid "" "Decode the JSON Web Token string with the given key, and validate it with" " the claims requests." msgstr "使用给定的密钥解码 JSON Web Token 字符串,并使用声明请求进行验证。" #: joserfc.jwt.decode:4 of msgid "text of the JWT" msgstr "JWT 的文本" #: joserfc.jwt.decode:5 of msgid "key used to verify the signature" msgstr "用于验证签名的密钥" #: joserfc.jwt.decode:7 joserfc.jwt.encode:7 of msgid "a ``JWSRegistry`` or ``JWERegistry`` to use" msgstr "要使用的 ``JWSRegistry`` 或 ``JWERegistry``" #: joserfc.jwt.decode:8 of msgid "A JSONDecoder subclass to use" msgstr "" #: joserfc.jwt.decode:9 of msgid "BadSignatureError" msgstr "BadSignatureError" #: joserfc.jwt.encode:1 of msgid "Encode a JSON Web Token with the given header, and claims." msgstr "使用给定的 header 和声明 (claims) 编码 JSON Web Token。" #: joserfc.jwt.encode:3 of msgid "A dict of the JWT header" msgstr "字典形式的 JWT header 部分" #: joserfc.jwt.encode:4 of msgid "A dict of the JWT claims to be encoded" msgstr "用来编码的字典形式的 JWT claims 部分" #: joserfc.jwt.encode:5 of msgid "key used to sign the signature" msgstr "用于签名的密钥" #: joserfc.jwt.encode:8 of msgid "A JSONEncoder subclass to use" msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/changelog.po000066400000000000000000000220111501424510600222050ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-24 13:25+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../changelog.rst:2 msgid "Changelog" msgstr "历史记录" #: ../../changelog.rst:6 msgid "Here is the history of joserfc_ package releases." msgstr "这里记录了 joserfc_ 的发布历史。" #: ../../changelog.rst:16 msgid "1.1.0" msgstr "" #: ../../changelog.rst:18 msgid "**Released on May 24, 2025**" msgstr "" #: ../../changelog.rst:20 msgid "Use \"import as\" to prioritize the modules for editors." msgstr "" #: ../../changelog.rst:21 msgid "" "Added parameter ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` " "for ``jwt.decode``." msgstr "" #: ../../changelog.rst:22 msgid "Added ``none`` algorithm for JWS." msgstr "" #: ../../changelog.rst:23 msgid "Added ``jwk.import_key`` and ``jwk.generate_key`` aliases." msgstr "" #: ../../changelog.rst:25 ../../changelog.rst:138 msgid "**Breaking changes**:" msgstr "" #: ../../changelog.rst:27 msgid "Use ``ECKey.binding.register_curve`` to register new supported curves." msgstr "" #: ../../changelog.rst:28 msgid "" "Use ``UnsupportedAlgorithmError`` instead of ``ValueError`` in JWS/JWE " "registry." msgstr "" #: ../../changelog.rst:29 msgid "Use ``MissingKeyTypeError`` and ``InvalidKeyIdError`` for errors in JWK." msgstr "" #: ../../changelog.rst:30 msgid "" "Use ``UnsupportedHeaderError``, ``MissingHeaderError``, and " "``MissingCritHeaderError`` for header validation." msgstr "" #: ../../changelog.rst:31 msgid "Respect RFC6749 character set in error descriptions." msgstr "" #: ../../changelog.rst:34 msgid "1.0.4" msgstr "" #: ../../changelog.rst:36 msgid "**Released on Feb 28, 2025**" msgstr "" #: ../../changelog.rst:38 msgid "Use secrets module to generate random bytes." msgstr "" #: ../../changelog.rst:39 msgid "" "Use warnings for possible unsafe ``OctKey``` instead of raising error, " "via :issue:`32`." msgstr "" #: ../../changelog.rst:42 msgid "1.0.3" msgstr "" #: ../../changelog.rst:44 msgid "**Released on Feb 6, 2025**" msgstr "" #: ../../changelog.rst:46 msgid "Allow using sha256, sha384, sha512 hash functions in thumbprint (RFC7638)." msgstr "" #: ../../changelog.rst:49 msgid "1.0.2" msgstr "" #: ../../changelog.rst:51 msgid "**Released on Jan 20, 2025**" msgstr "" #: ../../changelog.rst:53 msgid "Support import key from a certificate pem file." msgstr "" #: ../../changelog.rst:56 msgid "1.0.1" msgstr "" #: ../../changelog.rst:58 msgid "**Released on December 3, 2024**" msgstr "" #: ../../changelog.rst:60 msgid "Throw an error on non-valid base64 strings." msgstr "" #: ../../changelog.rst:63 msgid "1.0.0" msgstr "" #: ../../changelog.rst:65 msgid "**Released on July 14, 2024**" msgstr "" #: ../../changelog.rst:67 msgid "Fix type hints for strict mode." msgstr "" #: ../../changelog.rst:70 msgid "0.12.0" msgstr "" #: ../../changelog.rst:72 msgid "**Released on June 15, 2024**" msgstr "" #: ../../changelog.rst:74 msgid "Limit DEF decompress size to 250k bytes." msgstr "" #: ../../changelog.rst:75 msgid "Fix claims validation, via :issue:`23`." msgstr "" #: ../../changelog.rst:78 msgid "0.11.1" msgstr "" #: ../../changelog.rst:80 ../../changelog.rst:87 msgid "**Released on June 4, 2024**" msgstr "" #: ../../changelog.rst:82 msgid "Remove validating ``typ`` header with ``jwt.decode`` method." msgstr "" #: ../../changelog.rst:85 msgid "0.11.0" msgstr "" #: ../../changelog.rst:89 msgid "``jwe.decrypt_json`` allows to verify only one recipient." msgstr "" #: ../../changelog.rst:90 msgid "Prevent ``OctKey`` to import ``ssh-dss``." msgstr "" #: ../../changelog.rst:91 msgid "Deprecate use of string and bytes as key." msgstr "" #: ../../changelog.rst:94 msgid "0.10.0" msgstr "" #: ../../changelog.rst:96 msgid "**Released on May 13, 2024**" msgstr "" #: ../../changelog.rst:98 msgid "Change ``jwt.encode`` and ``jwt.decode`` to use JWS by default." msgstr "" #: ../../changelog.rst:101 msgid "0.9.0" msgstr "" #: ../../changelog.rst:103 msgid "**Released on November 16, 2023**" msgstr "" #: ../../changelog.rst:105 msgid "Use ``os.urandom`` for ``OctKey.generate_key``." msgstr "" #: ../../changelog.rst:106 msgid "Add ``allow_blank`` for ``JWTClaimsRegistry``." msgstr "" #: ../../changelog.rst:107 msgid "Improve callable key for :meth:`~jwk.guess_key`." msgstr "" #: ../../changelog.rst:110 msgid "0.8.0" msgstr "" #: ../../changelog.rst:112 msgid "**Released on September 06, 2023**" msgstr "" #: ../../changelog.rst:114 msgid "Add :ref:`ensure_kid` method on key models." msgstr "" #: ../../changelog.rst:115 msgid "Add ``auto_kid`` parameter on key model ``.generate_key`` method." msgstr "" #: ../../changelog.rst:116 ../../changelog.rst:126 msgid "Improvements on type hints" msgstr "" #: ../../changelog.rst:119 msgid "0.7.0" msgstr "" #: ../../changelog.rst:121 msgid "**Released on August 14, 2023**" msgstr "" #: ../../changelog.rst:123 msgid "Add \"iat\" claims validation in JWT." msgstr "" #: ../../changelog.rst:124 msgid "Add ``__bool__`` magic method on :class:`jwk.KeySet`." msgstr "" #: ../../changelog.rst:125 msgid "" "Raise ``InvalidExchangeKeyError`` for ``exchange_derive_key`` on Curve " "key." msgstr "" #: ../../changelog.rst:129 msgid "0.6.0" msgstr "" #: ../../changelog.rst:131 msgid "**Released on July 20, 2023**" msgstr "" #: ../../changelog.rst:133 msgid "Huge improvements on type hints, via :user:`Viicos`." msgstr "" #: ../../changelog.rst:134 msgid "Do not mutate the header when ``jwt.encode``, via :issue:`6`." msgstr "" #: ../../changelog.rst:135 msgid "Register algorithms with their matched key types on key set." msgstr "" #: ../../changelog.rst:136 msgid "Improve error handling, raise proper errors." msgstr "" #: ../../changelog.rst:140 msgid "" "``jws.JSONSignature`` is replaced by :class:`jws.GeneralJSONSignature` " "and :class:`jws.FlattenedJSONSignature`." msgstr "" #: ../../changelog.rst:142 msgid "" "``jwe.JSONEncryption`` is replaced by :class:`jwe.GeneralJSONEncryption` " "and :class:`jwe.FlattenedJSONEncryption`." msgstr "" #: ../../changelog.rst:146 msgid "0.5.0" msgstr "" #: ../../changelog.rst:148 msgid "**Released on July 12, 2023**" msgstr "" #: ../../changelog.rst:150 msgid "Add RFC7797 JSON Web Signature (JWS) Unencoded Payload Option" msgstr "" #: ../../changelog.rst:151 msgid "Fix ``decrypt_json`` when there is no ``encrypted_key``" msgstr "" #: ../../changelog.rst:152 msgid "Rename JWE CompleteJSONSerialization to GeneralJSONSerialization" msgstr "" #: ../../changelog.rst:153 msgid "Rename ``JSONEncryption.flatten`` to ``.flattened``" msgstr "" #: ../../changelog.rst:154 msgid "Load and dump RSA, EC, and OKP key with password" msgstr "" #: ../../changelog.rst:155 msgid "" "Rename Curve key method: ``exchange_shared_key`` to " "``exchange_derive_key``" msgstr "" #: ../../changelog.rst:158 msgid "0.4.0" msgstr "" #: ../../changelog.rst:160 msgid "**Released on July 6, 2023**" msgstr "" #: ../../changelog.rst:162 msgid "Change ``options`` to ``parameters`` for JWK methods" msgstr "" #: ../../changelog.rst:163 msgid "Change ``JWSRegistry`` and ``JWERegistry`` parameters" msgstr "" #: ../../changelog.rst:164 msgid "Guess ``sender_key`` from JWKs in JWE" msgstr "" #: ../../changelog.rst:165 msgid "Add importing key from DER encoding bytes" msgstr "" #: ../../changelog.rst:166 msgid "Fix JWS JSON serialization when members have only unprotected headers" msgstr "" #: ../../changelog.rst:167 msgid "Check key type before processing algorithms of JWS and JWE" msgstr "" #: ../../changelog.rst:170 msgid "0.3.0" msgstr "" #: ../../changelog.rst:172 msgid "**Released on June 29, 2023**" msgstr "" #: ../../changelog.rst:174 msgid "Return ``str`` instead of ``bytes`` for JWS and JWE serializations" msgstr "" #: ../../changelog.rst:175 msgid "Add a ``detach_content`` method for JWS" msgstr "" #: ../../changelog.rst:176 msgid "Remove ``jwt.extract`` method, because ``extract`` won't work for JWE" msgstr "" #: ../../changelog.rst:177 msgid "Add ``JWKRegistry`` for JWK" msgstr "" #: ../../changelog.rst:178 msgid "Update ``JSONEncryption.add_recipient`` parameters" msgstr "" #: ../../changelog.rst:179 msgid "Export register methods for JWE drafts" msgstr "" #: ../../changelog.rst:182 msgid "0.2.0" msgstr "" #: ../../changelog.rst:184 msgid "**Released on June 25, 2023**" msgstr "" #: ../../changelog.rst:186 msgid "A beta release." msgstr "" #: ../../changelog.rst:189 msgid "0.1.0" msgstr "" #: ../../changelog.rst:191 msgid "**Released on March 5, 2023**" msgstr "" #: ../../changelog.rst:193 msgid "Initial release." msgstr "" #~ msgid "Unreleased" #~ msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/contributing.po000066400000000000000000000222041501424510600227710ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-02-28 11:54+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../contributing/authors.rst:2 msgid "Authors" msgstr "作者列表" #: ../../contributing/authors.rst:4 msgid "" "``joserfc`` is written and maintained by `Hsiaoming Yang " "`_." msgstr "" #: ../../contributing/authors.rst:8 msgid "Contributors" msgstr "贡献者" #: ../../contributing/authors.rst:10 msgid "Here is the list of the main contributors:" msgstr "" #: ../../contributing/authors.rst:15 msgid "And more on https://github.com/authlib/joserfc/graphs/contributors" msgstr "" #: ../../contributing/index.rst:2 msgid "Contributing" msgstr "参与贡献" #: ../../contributing/index.rst:4 msgid "Contributions are welcome, and they are greatly appreciated!" msgstr "欢迎贡献,我们非常感激你的参与!" #: ../../contributing/index.rst:7 msgid "Types of contributions" msgstr "贡献类型" #: ../../contributing/index.rst:9 msgid "There are many ways you can contribute." msgstr "您可以通过多种方式做出贡献。" #: ../../contributing/index.rst:12 msgid "Report bugs" msgstr "报告错误" #: ../../contributing/index.rst:14 msgid "" "You're welcome to report bugs at `GitHub Issues " "`_." msgstr "欢迎使用 `GitHub Issues `_ 来报告错误。" #: ../../contributing/index.rst:17 msgid "" "Before reporting a bug, please verify your bug against the latest code in" " ``main`` branch." msgstr "在向我们报告错误前,请先使用 ``main`` 分支的最新代码检查一下,也许该错误已修复。" #: ../../contributing/index.rst:20 msgid "When reporting a bug, please including:" msgstr "报告错误时,请包含:" #: ../../contributing/index.rst:22 msgid "Your operating system name and version." msgstr "你的操作系统以及操作系统的版本。" #: ../../contributing/index.rst:23 msgid "Your Python version." msgstr "你的 Python 版本。" #: ../../contributing/index.rst:24 msgid "Details to reproduce the bug." msgstr "重现错误的步骤。" #: ../../contributing/index.rst:27 msgid "Submit fixes" msgstr "提交修改" #: ../../contributing/index.rst:29 msgid "" "Once you found a bug that you can fix, you're welcome to submit your pull" " request." msgstr "一旦你发现了一个可以修复的错误,欢迎向我们提交 Pull Request。" #: ../../contributing/index.rst:32 msgid "" "Please follow our `git commit conventions " "`_." msgstr "" #: ../../contributing/index.rst:35 msgid "Improve documentation" msgstr "优化文档" #: ../../contributing/index.rst:37 msgid "" "Everyone wants a good documentation. There may be mistakes or things " "missing in the documentation, you're welcome to help us improving the " "documentation." msgstr "" #: ../../contributing/index.rst:44 msgid "Development" msgstr "开发" #: ../../contributing/index.rst:46 msgid "" "Once you cloned ``joserfc``'s source code, you can setup a development " "environment to work on." msgstr "" #: ../../contributing/index.rst:50 msgid "venv" msgstr "" #: ../../contributing/index.rst:52 msgid "I strongly suggest you create a virtual environment with ``venv``:" msgstr "强烈建议你使用 ``venv`` 创建一个虚拟环境:" #: ../../contributing/index.rst:60 msgid "Install" msgstr "安装" #: ../../contributing/index.rst:62 msgid "Then install the Python requirements for development:" msgstr "" #: ../../contributing/index.rst:69 msgid "Run tests" msgstr "" #: ../../contributing/index.rst:71 msgid "" "Once you made some code changes, you can add your test case in the " "``tests`` folder, then verify it with:" msgstr "" #: ../../contributing/index.rst:79 msgid "Next" msgstr "下一步" #: ../../contributing/sponsors.rst:2 msgid "Sponsors" msgstr "赞助者" #: ../../contributing/structure.rst:2 msgid "Code structure" msgstr "代码结构" #: ../../contributing/structure.rst:4 msgid "" "The code structure of ``joserfc`` follows an organized approach based on " "RFC specifications. It is designed to enhance understanding by grouping " "the code according to specific RFCs." msgstr "" #: ../../contributing/structure.rst:8 msgid "Overview" msgstr "概要" #: ../../contributing/structure.rst:10 msgid "The overall structure is organized as follows:" msgstr "" #: ../../contributing/structure.rst:28 msgid "" "This structure allows developers to easily navigate and comprehend each " "RFC specification individually. The code is organized from low-level to " "high-level, making it intuitive and convenient to understand and use. " "Developers can utilize the higher-level APIs (``jws.py``, ``jwe.py``, " "``jwk.py``, ``jwt.py``) without needing to delve into the lower-level " "implementation details." msgstr "" #: ../../contributing/structure.rst:34 msgid "" "By following this structured approach, joserfc ensures clarity, ease of " "understanding, and simplicity in both comprehension and utilization of " "the library." msgstr "" #: ../../contributing/structure.rst:38 msgid "New RFCs" msgstr "新增 RFCs" #: ../../contributing/structure.rst:40 msgid "" "To add a new RFC implementation to ``joserfc``, you can follow a " "straightforward approach:" msgstr "" #: ../../contributing/structure.rst:42 msgid "" "Create a new folder within the ``joserfc`` package, named after the RFC " "number." msgstr "" #: ../../contributing/structure.rst:43 msgid "" "Place the relevant code files and modules related to the new RFC within " "the created folder." msgstr "" #: ../../contributing/structure.rst:44 msgid "" "Organize the code structure within the folder to align with the RFC's " "specifications and guidelines." msgstr "" #: ../../contributing/structure.rst:45 msgid "" "Update the necessary high-level APIs (``jws.py``, ``jwe.py``, ``jwk.py``," " ``jwt.py``) to integrate and expose the new RFC implementation." msgstr "" #: ../../contributing/structure.rst:48 msgid "" "By adhering to this approach, you can easily incorporate new RFC " "implementations into ``joserfc``, maintaining a well-organized and " "extensible codebase." msgstr "" #: ../../contributing/structure.rst:52 msgid "Draft RFCs" msgstr "草案" #: ../../contributing/structure.rst:54 msgid "" "Draft RFCs are specifications that are still in the draft phase and " "subject to potential changes. In ``joserfc``, draft implementations are " "placed within the ``joserfc.drafts`` package. It's important to note that" " draft implementations are not typically accepted as part of the main " "``joserfc`` library until the RFC is officially published and stabilized." msgstr "" #: ../../contributing/structure.rst:59 msgid "" "Although draft implementations are included within the ``joserfc.drafts``" " package for exploration and experimentation purposes, they may not fully" " adhere to the final version of the RFC. It is recommended to use caution" " when relying on draft implementations, as they may undergo significant " "changes or be incompatible with the final RFC specification." msgstr "" #: ../../contributing/translation.rst:4 msgid "Translations" msgstr "翻译" #: ../../contributing/translation.rst:6 msgid "" "To begin translating this documentation into other languages, please " "start by referring to the :ref:`development` guide, which will help you " "set up a suitable development environment. Afterward, navigate to the " "docs folder using the following command:" msgstr "" #: ../../contributing/translation.rst:16 msgid "Generate .pot files" msgstr "生成 .pot 文件" #: ../../contributing/translation.rst:18 msgid "" "Before creating translations in your desired languages, you need to " "generate the source ``.pot`` files. This can be accomplished using the " "following command:" msgstr "" #: ../../contributing/translation.rst:27 msgid "Update languages" msgstr "更新语言包" #: ../../contributing/translation.rst:29 msgid "" "Next, proceed to generate the ``.po`` files in your preferred languages " "using the ``sphinx-intl`` tool:" msgstr "下一步,使用 ``sphinx-intl`` 这个工具来生成你需要的语言的 ``.po`` 文件:" #: ../../contributing/translation.rst:36 msgid "In this example, we're using the language code ``de`` to represent German." msgstr "在这个例子中,我们使用语言代码 ``de`` 代表德语。" #: ../../contributing/translation.rst:39 msgid "Writing the Translations" msgstr "更新翻译文件" #: ../../contributing/translation.rst:41 msgid "" "Following the previous command, the ``.po`` files will be generated " "within the ``locales/de/LC_MESSAGES`` directory. You can now edit these " "files to add the German translations accordingly." msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/guide.po000066400000000000000000001502261501424510600213650ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-24 13:25+0900\n" "PO-Revision-Date: 2023-07-15 14:44+0900\n" "Last-Translator: Hsiaoming Yang \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../guide/algorithms.rst:6 ../../guide/jws.rst:239 #: ../../guide/registry.rst:21 msgid "Algorithms" msgstr "算法" #: ../../guide/algorithms.rst:10 msgid "All available algorithms for JWS, JWE, JWK, and JWT." msgstr "JWS、JWE、JWK 和 JWT 所有可用的算法。" #: ../../guide/algorithms.rst:14 msgid "" "This documentation describes the algorithms to be used with JSON Web " "Signature (JWS), JSON Web Encryption (JWE), and JSON Web Key (JWK)." msgstr "" "本文档描述了在 JSON Web Signature (JWS)、JSON Web Encryption (JWE) 和 JSON Web Key" " (JWK) 中使用的算法。" #: ../../guide/algorithms.rst:19 ../../guide/index.rst:69 ../../guide/jwk.rst:6 msgid "JSON Web Key" msgstr "" #: ../../guide/algorithms.rst:21 msgid "The JSON Web Key (JWK) algorithms contains:" msgstr "JSON Web Key(JWK)的算法包括:" #: ../../guide/algorithms.rst:23 msgid "" ":ref:`OctKey` : accepts key size in bits, which means the ``key_size`` " "MUST be dividable by 8." msgstr ":ref:`OctKey` :接受以比特为单位的密钥大小,这意味着 ``key_size`` 必须是 8 的倍数。" #: ../../guide/algorithms.rst:24 msgid "" ":ref:`RSAKey` : accepts key size in bits, ``key_size`` MUST ``>=512`` and" " dividable by 8." msgstr ":ref:`RSAKey` :接受以比特为单位的密钥大小,``key_size`` 必须 ``>=512`` 且为 8 的倍数。" #: ../../guide/algorithms.rst:25 msgid "" ":ref:`ECKey` : accepts ``crv`` with ``P-256``, ``P-384``, ``P-521``, and " "``secp256k1``." msgstr ":ref:`ECKey` :接受 ``crv`` 为 ``P-256``、``P-384``、``P-521`` 和 ``secp256k1``。" #: ../../guide/algorithms.rst:26 msgid "" ":ref:`OKPKey` : accepts ``crv`` with ``Ed25519``, ``Ed448``, ``X25519``, " "and ``X448``." msgstr ":ref:`OKPKey` :接受 ``crv`` 为 ``Ed25519``、``Ed448``、``X25519`` 和 ``X448``。" #: ../../guide/algorithms.rst:31 ../../guide/index.rst:81 ../../guide/jws.rst:6 msgid "JSON Web Signature" msgstr "" #: ../../guide/algorithms.rst:33 msgid "" "``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, and " "RFC8812. You MUST specify the correct key type for each algorithm." msgstr "``joserfc.jws`` 模块支持 RFC7518、RFC8037 和 RFC8812 中的算法。您必须为每个算法指定正确的密钥类型。" #: ../../guide/algorithms.rst:37 ../../guide/algorithms.rst:85 #: ../../guide/jws.rst:245 msgid "Algorithm name" msgstr "算法名" #: ../../guide/algorithms.rst:37 ../../guide/algorithms.rst:85 msgid "Key Type" msgstr "密钥类型" #: ../../guide/algorithms.rst:37 ../../guide/algorithms.rst:85 #: ../../guide/jws.rst:245 msgid "Recommended" msgstr "是推荐" #: ../../guide/algorithms.rst:39 ../../guide/jws.rst:247 msgid "none" msgstr "" #: ../../guide/algorithms.rst:39 ../../guide/algorithms.rst:40 #: ../../guide/algorithms.rst:41 ../../guide/algorithms.rst:42 #: ../../guide/algorithms.rst:87 ../../guide/algorithms.rst:88 #: ../../guide/algorithms.rst:89 ../../guide/algorithms.rst:90 #: ../../guide/algorithms.rst:98 ../../guide/algorithms.rst:99 #: ../../guide/algorithms.rst:100 ../../guide/jwk.rst:19 msgid "OctKey" msgstr "" #: ../../guide/algorithms.rst:39 ../../guide/algorithms.rst:41 #: ../../guide/algorithms.rst:42 ../../guide/algorithms.rst:44 #: ../../guide/algorithms.rst:45 ../../guide/algorithms.rst:47 #: ../../guide/algorithms.rst:48 ../../guide/algorithms.rst:49 #: ../../guide/algorithms.rst:50 ../../guide/algorithms.rst:51 #: ../../guide/algorithms.rst:52 ../../guide/algorithms.rst:53 #: ../../guide/algorithms.rst:89 ../../guide/algorithms.rst:91 #: ../../guide/algorithms.rst:93 ../../guide/algorithms.rst:96 #: ../../guide/algorithms.rst:98 ../../guide/algorithms.rst:99 #: ../../guide/algorithms.rst:100 ../../guide/algorithms.rst:101 #: ../../guide/algorithms.rst:102 ../../guide/algorithms.rst:103 #: ../../guide/jws.rst:247 ../../guide/jws.rst:249 ../../guide/jws.rst:250 #: ../../guide/jws.rst:252 ../../guide/jws.rst:253 ../../guide/jws.rst:255 #: ../../guide/jws.rst:256 ../../guide/jws.rst:257 ../../guide/jws.rst:258 #: ../../guide/jws.rst:259 ../../guide/jws.rst:260 ../../guide/jws.rst:261 msgid ":bdg-danger:`No`" msgstr ":bdg-danger:`否`" #: ../../guide/algorithms.rst:40 ../../guide/jws.rst:248 msgid "HS256" msgstr "" #: ../../guide/algorithms.rst:40 ../../guide/algorithms.rst:43 #: ../../guide/algorithms.rst:46 ../../guide/algorithms.rst:87 #: ../../guide/algorithms.rst:88 ../../guide/algorithms.rst:90 #: ../../guide/algorithms.rst:92 ../../guide/algorithms.rst:94 #: ../../guide/algorithms.rst:95 ../../guide/algorithms.rst:97 msgid ":bdg-success:`Yes`" msgstr ":bdg-success:`是`" #: ../../guide/algorithms.rst:41 ../../guide/jws.rst:249 msgid "HS384" msgstr "" #: ../../guide/algorithms.rst:42 ../../guide/jws.rst:250 msgid "HS512" msgstr "" #: ../../guide/algorithms.rst:43 ../../guide/jws.rst:251 msgid "RS256" msgstr "" #: ../../guide/algorithms.rst:43 ../../guide/algorithms.rst:44 #: ../../guide/algorithms.rst:45 ../../guide/algorithms.rst:49 #: ../../guide/algorithms.rst:50 ../../guide/algorithms.rst:51 #: ../../guide/algorithms.rst:91 ../../guide/algorithms.rst:92 #: ../../guide/algorithms.rst:93 ../../guide/algorithms.rst:101 #: ../../guide/algorithms.rst:102 ../../guide/algorithms.rst:103 #: ../../guide/jwk.rst:64 msgid "RSAKey" msgstr "" #: ../../guide/algorithms.rst:44 ../../guide/jws.rst:252 msgid "RS384" msgstr "" #: ../../guide/algorithms.rst:45 ../../guide/jws.rst:253 msgid "RS512" msgstr "" #: ../../guide/algorithms.rst:46 ../../guide/jws.rst:254 msgid "ES256" msgstr "" #: ../../guide/algorithms.rst:46 ../../guide/algorithms.rst:47 #: ../../guide/algorithms.rst:48 ../../guide/algorithms.rst:53 #: ../../guide/algorithms.rst:94 ../../guide/algorithms.rst:95 #: ../../guide/algorithms.rst:96 ../../guide/algorithms.rst:97 #: ../../guide/jwk.rst:125 msgid "ECKey" msgstr "" #: ../../guide/algorithms.rst:47 ../../guide/jws.rst:255 msgid "ES384" msgstr "" #: ../../guide/algorithms.rst:48 ../../guide/jws.rst:256 msgid "ES512" msgstr "" #: ../../guide/algorithms.rst:49 ../../guide/jws.rst:257 msgid "PS256" msgstr "" #: ../../guide/algorithms.rst:50 ../../guide/jws.rst:258 msgid "PS384" msgstr "" #: ../../guide/algorithms.rst:51 ../../guide/jws.rst:259 msgid "PS512" msgstr "" #: ../../guide/algorithms.rst:52 ../../guide/jws.rst:260 msgid "EdDSA" msgstr "" #: ../../guide/algorithms.rst:52 ../../guide/algorithms.rst:124 #: ../../guide/jwk.rst:180 msgid "OKPKey" msgstr "" #: ../../guide/algorithms.rst:53 ../../guide/jws.rst:261 msgid "ES256K" msgstr "" #: ../../guide/algorithms.rst:57 msgid "" "``EdDSA`` algorithm only accepts ``OKPKey`` with \"crv\" of \"Ed25519\" " "and \"Ed448\"." msgstr "``EdDSA`` 算法只接受具有 \"crv\" 为 \"Ed25519\" 和 \"Ed448\" 的 ``OKPKey``。" #: ../../guide/algorithms.rst:59 msgid "" "By default, JWS ``serialize`` and ``deserialize`` methods will ONLY allow" " recommended algorithms. To use non-recommended algorithms, developers " "MUST explicitly specify the algorithms either by the ``algorithms`` " "parameter, or ``registry`` parameter." msgstr "" #: ../../guide/algorithms.rst:79 ../../guide/index.rst:87 ../../guide/jwe.rst:6 msgid "JSON Web Encryption" msgstr "" #: ../../guide/algorithms.rst:81 msgid "" "``joserfc.jwe`` module supports algorithms from RFC7518, and drafts of " "``ECDH-1PU``. You MUST specify the correct key type for each algorithm." msgstr "``joserfc.jwe`` 模块支持 RFC7518 中的算法,以及 ``ECDH-1PU`` 的草案。您必须为每个算法指定正确的密钥类型。" #: ../../guide/algorithms.rst:87 msgid "dir" msgstr "" #: ../../guide/algorithms.rst:88 msgid "A128KW" msgstr "" #: ../../guide/algorithms.rst:89 msgid "A192KW" msgstr "" #: ../../guide/algorithms.rst:90 msgid "A256KW" msgstr "" #: ../../guide/algorithms.rst:91 msgid "RSA1_5" msgstr "" #: ../../guide/algorithms.rst:92 msgid "RSA-OAEP" msgstr "" #: ../../guide/algorithms.rst:93 msgid "RSA-OAEP-256" msgstr "" #: ../../guide/algorithms.rst:94 ../../guide/algorithms.rst:129 msgid "ECDH-ES" msgstr "" #: ../../guide/algorithms.rst:95 ../../guide/algorithms.rst:130 msgid "ECDH-ES+A128KW" msgstr "" #: ../../guide/algorithms.rst:96 ../../guide/algorithms.rst:131 msgid "ECDH-ES+A192KW" msgstr "" #: ../../guide/algorithms.rst:97 ../../guide/algorithms.rst:132 msgid "ECDH-ES+A256KW" msgstr "" #: ../../guide/algorithms.rst:98 msgid "A128GCMKW" msgstr "" #: ../../guide/algorithms.rst:99 msgid "A192GCMKW" msgstr "" #: ../../guide/algorithms.rst:100 msgid "A256GCMKW" msgstr "" #: ../../guide/algorithms.rst:101 msgid "PBES2-HS256+A128KW" msgstr "" #: ../../guide/algorithms.rst:102 msgid "PBES2-HS384+A192KW" msgstr "" #: ../../guide/algorithms.rst:103 msgid "PBES2-HS512+A256KW" msgstr "" #: ../../guide/algorithms.rst:106 msgid "" "All algorithms defined in RFC7518 for \"enc\" value are recommended, " "which including:" msgstr "RFC7518 中定义的所有用于 \"enc\" 值的算法都是推荐使用的,包括:" #: ../../guide/algorithms.rst:109 msgid "``A128CBC-HS256``" msgstr "" #: ../../guide/algorithms.rst:110 msgid "``A192CBC-HS384``" msgstr "" #: ../../guide/algorithms.rst:111 msgid "``A256CBC-HS512``" msgstr "" #: ../../guide/algorithms.rst:112 msgid "``A128GCM``" msgstr "" #: ../../guide/algorithms.rst:113 msgid "``A192GCM``" msgstr "" #: ../../guide/algorithms.rst:114 msgid "``A256GCM``" msgstr "" #: ../../guide/algorithms.rst:116 msgid "" "A ``DEF`` algorithm for the \"zip\" (compression) header parameter is " "also defined in RFC7518, which is recommended." msgstr "RFC7518 还定义了用于 \"zip\"(压缩)头参数的 ``DEF`` 算法,该算法推荐使用。" #: ../../guide/algorithms.rst:119 msgid "" "There are also additional algorithms for \"alg\" and \"enc\" in draft " "versions. Please refer to the following sections for more information." msgstr "草案版本中还有针对 \"alg\" 和 \"enc\" 的额外算法。请参考以下章节获取更多信息。" #: ../../guide/algorithms.rst:126 msgid "" "You can use ``OKPKey`` with the \"crv\" (curve) parameter set to " "``X25519`` or ``X448`` for the following algorithms:" msgstr "对于以下算法,您可以使用 ``OKPKey`` 并将 \"crv\"(曲线)参数设置为 ``X25519`` 或 ``X448``:" #: ../../guide/algorithms.rst:134 msgid "" "This allows you to utilize these elliptic curve algorithms with " "``OKPKey`` for your cryptographic operations." msgstr "" #: ../../guide/algorithms.rst:140 msgid "C20P and XC20P" msgstr "C20P 与 XC20P" #: ../../guide/algorithms.rst:142 msgid "" "``C20P`` and ``XC20P`` algorithms are still in drafts, they are not " "registered by default. To use ``C20P`` and ``XC20P``, developers have to " "install the ``PyCryptodome`` module." msgstr "" "``C20P`` 和 ``XC20P`` 算法仍处于草案阶段,默认状态下他们未注册到 ``JWERegistry``。要使用 ``C20P`` 和" " ``XC20P``,开发人员必须安装 ``PyCryptodome`` 模块。" #: ../../guide/algorithms.rst:149 msgid "" "This is caused by ``cryptography`` package does only support \"ChaCha20\"" " cipher, not **XChaCha20**, while ``pycryptodome`` supports both " "\"ChaCha20\" and \"XChaCha20\" ciphers." msgstr "" "这是由于 ``cryptography`` 库只支持 \"ChaCha20\" 密码算法,不支持 **XChaCha20**,而 " "``pycryptodome`` 同时支持 \"ChaCha20\" 和 \"XChaCha20\" 密码算法。" #: ../../guide/algorithms.rst:153 msgid "Register ciphers" msgstr "注册密码算法" #: ../../guide/algorithms.rst:155 msgid "" "The default :ref:`registry` doesn't contain draft ciphers, developers " "MUST register ``C20P`` and ``XC20P`` at first:" msgstr "默认的 :ref:`registry` 不包含草案算法,开发人员必须先注册 ``C20P`` 和 ``XC20P``:" #: ../../guide/algorithms.rst:165 msgid "Use custom ``registry``" msgstr "自定义注册表 ``registry``" #: ../../guide/algorithms.rst:170 msgid "" "Use a custom ``registry`` in :meth:`encrypt_compact`, " ":meth:`decrypt_compact`, :meth:`encrypt_json`, and :meth:`decrypt_json`." msgstr "" "在 :meth:`encrypt_compact`、:meth:`decrypt_compact`、:meth:`encrypt_json` 和 " ":meth:`decrypt_json` 中使用自定义 ``registry``。" #: ../../guide/algorithms.rst:195 msgid "ECDH-1PU algorithms" msgstr "ECDH-1PU 相关算法" #: ../../guide/algorithms.rst:197 msgid "" "Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model " "(ECDH-1PU) are still in drafts, they are not registered by default. To " "use ``ECDH-1PU`` related algorithms, developers MUST register them " "manually:" msgstr "" "椭圆曲线迪菲-赫尔曼一次通用模型(ECDH-1PU)密钥协商仍然处于草案阶段,默认情况下未注册。要使用与 ``ECDH-1PU`` " "相关的算法,开发人员必须手动注册它们:" #: ../../guide/algorithms.rst:207 msgid "" "Then use a custom ``registry`` with the required ``ECDH-1PU`` algorithms." " For instance:" msgstr "然后使用带有所需的 ``ECDH-1PU`` 算法的自定义 ``registry``。例如:" #: ../../guide/algorithms.rst:230 msgid "" "The ``ECDH-1PU`` algorithms require a **sender key**, which MUST be a " "private key when calling :meth:`encrypt_compact` and :meth:`encrypt_json`" " methods." msgstr "" "``ECDH-1PU`` 算法需要一个 **发送方密钥**,在调用 :meth:`encrypt_compact` 和 " ":meth:`encrypt_json` 方法时,该密钥必须是私钥。" #: ../../guide/algorithms.rst:233 msgid "" "The ``sender_key`` can be a :class:`~joserfc.jwk.KeySet`, and JWE will " "find the correct key according to ``skid`` header value." msgstr "" "``sender_key`` 可以是 :class:`~joserfc.jwk.KeySet`,JWE 会根据 ``skid`` " "头部值查找正确的密钥。" #: ../../guide/index.rst:4 msgid "Guide" msgstr "教程" #: ../../guide/index.rst:6 msgid "" "This section provides a quick overview of how to get started with " "``joserfc`` and perform encoding and decoding a JWT." msgstr "本章节提供了关于如何开始使用 ``joserfc`` 并进行 JWT 编码和解码的快速概述。" #: ../../guide/index.rst:10 msgid "Encode and decode JWT" msgstr "JWT 的编码与解码" #: ../../guide/index.rst:28 msgid "Learn the details of :ref:`jwt` in the next chapter." msgstr "您可以在下一章节中了解 :ref:`jwt` 的详细信息。" #: ../../guide/index.rst:31 msgid "Import and generate JWK" msgstr "JWK 的导入与导出" #: ../../guide/index.rst:58 msgid "Learn the details of :ref:`jwk` in the next chapter." msgstr "您可以在下一章节中了解 :ref:`jwk` 的详细信息。" #: ../../guide/index.rst:61 msgid "Dive deep" msgstr "深入了解" #: ../../guide/index.rst:63 msgid "Next, learn each module in details." msgstr "接下来,详细学习下面的每个模块。" #: ../../guide/index.rst:73 msgid "" "Learn how to use ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and JSON " "Web Key Set." msgstr "学习如何使用 ``OctKey``、``RSAKey``、``ECKey``、``OKPKey`` 以及 JSON Web Key Set。" #: ../../guide/index.rst:75 ../../guide/jwt.rst:6 msgid "JSON Web Token" msgstr "" #: ../../guide/index.rst:79 msgid "JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`." msgstr "JSON Web Token (JWT) 本质上是一种 :ref:`jws` 或者 :ref:`jwe`。" #: ../../guide/index.rst:85 msgid "Most :ref:`jwt` are encoded with JWS in compact serialization." msgstr "通常我们见到的 :ref:`jwt` 都是基于 JWS 的紧凑序列化编码。" #: ../../guide/index.rst:91 #, fuzzy msgid "" "JSON Web Encryption (JWE) represents encrypted content using JSON-based " "data structures." msgstr "JSON Web Encryption (JWE) 使用基于 JSON 的数据结构表示加密内容。(参考 RFC7516_)" #: ../../guide/introduction.rst:4 msgid "Introduction" msgstr "介绍" #: ../../guide/introduction.rst:6 msgid "" "``joserfc`` is a Python library that provides a comprehensive " "implementation of several essential JSON Object Signing and Encryption " "(JOSE) standards." msgstr "" "``joserfc`` 是一个 Python 库,提供了对多个重要的 JSON Object Signing and Encryption " "(JOSE) 标准的全面实现。" #: ../../guide/introduction.rst:9 msgid "" "Derived from Authlib_, ``joserfc`` offers a redesigned API specifically " "tailored to JOSE functionality, making it easier for developers to work " "with JWS, JWE, JWK, JWA, and JWT in their Python applications." msgstr "" "``joserfc`` 源自 Authlib_,专门为 JOSE 功能量身定制。同时我们重新设计了 API,让开发人员更容易在其 Python " "应用程序中处理 JWS、JWE、JWK、JWA 和 JWT。" #: ../../guide/introduction.rst:16 msgid "Features" msgstr "特点" #: ../../guide/introduction.rst:18 msgid "" "**Python Type Hints**: ``joserfc`` takes advantage of Python's type " "hinting capabilities, providing a more expressive and readable codebase. " "The use of type hints enhances development workflows by enabling better " "static analysis, improved IDE support, and more reliable code " "refactoring." msgstr "" "**Python 类型提示**:``joserfc`` 充分利用了 Python " "的类型提示功能,提供了更具表达力和可读性的代码库。使用类型提示可以增强开发工作流程,实现更好的静态分析、改善 IDE 支持和更可靠的代码重构。" #: ../../guide/introduction.rst:23 msgid "" "**Organized Codebase with RFC Compliance**: ``joserfc`` is structured " "following the RFC standards, ensuring clear separation and organization " "of the different JOSE functionalities. It strictly follows the latest " "versions of the JOSE standards, guaranteeing the highest level of " "interoperability and compliance." msgstr "" "**代码库组织有序且符合 RFC 标准**:``joserfc`` 的结构遵循 RFC 标准,确保不同的 JOSE " "功能之间的清晰分离和组织。它严格遵循最新的 JOSE 标准版本,确保最高水平的互操作性和合规性。" #: ../../guide/introduction.rst:29 msgid "Why joserfc?" msgstr "为何创建 joserfc" #: ../../guide/introduction.rst:31 msgid "" "``joserfc`` is derived from Authlib to facilitate easy maintenance and " "modularity. Previously, Authlib was developed as a mono library to design" " a comprehensive API that covered a wide range of authentication and " "security needs. However, as the project evolved, it became evident that " "splitting the modules from Authlib would improve maintainability and " "provide more focused and specialized libraries." msgstr "" "``joserfc`` 分离自 Authlib,以便于简化维护和模块化。之前,Authlib " "是作为一个单体库开发的,旨在设计一个涵盖广泛身份验证和安全需求的全面 API。然而,随着项目的发展,我们发现是时候将模块从 Authlib " "中拆分出来了,这将有助于提高项目的可维护性,并提供更专注和专业化的 JOSE 库。" #: ../../guide/introduction.rst:37 msgid "" "With ``joserfc``, developers can now benefit from a standalone library " "dedicated specifically to JOSE standards. This focused approach allows " "for better code organization, improved documentation, and a more " "streamlined development experience. By utilizing ``joserfc``, developers " "can confidently integrate JOSE functionalities into their projects, " "knowing that they are working with a dedicated and well-maintained " "solution." msgstr "" "``joserfc`` 是一个专门用于JOSE标准的 Python 库,方便开发人员处理 JOSE " "相关事务。这次改进不仅有助于更好地组织代码和文档,还能提供更流畅的开发体验。开发人员可以放心地使用``joserfc``将JOSE功能无缝集成到他们的项目中,确保他们正在使用一个专注并且维护良好的解决方案。" #: ../../guide/jwe.rst:11 msgid "" "JSON Web Encryption (JWE) represents encrypted content using JSON-based " "data structures. (via RFC7516_)" msgstr "JSON Web Encryption (JWE) 使用基于 JSON 的数据结构表示加密内容。(参考 RFC7516_)" #: ../../guide/jwe.rst:17 msgid "Compact Encryption" msgstr "紧凑型加密" #: ../../guide/jwe.rst:19 msgid "" "The JWE Compact Serialization represents encrypted content as a compact, " "URL-safe string. This string is:" msgstr "JWE 紧凑型(Compact)序列化将加密内容表示为紧凑的、URL 安全的字符串。该字符串为:" #: ../../guide/jwe.rst:30 ../../guide/jws.rst:29 msgid "" "An example of a compact serialization (line breaks for display purposes " "only):" msgstr "以下是一个紧凑型序列化的例子(换行符仅用于显示目的):" #: ../../guide/jwe.rst:47 ../../guide/jwe.rst:112 msgid "Encryption" msgstr "加密" #: ../../guide/jwe.rst:49 msgid "" "You can call :meth:`jwe.encrypt_compact` to construct a compact JWE " "serialization:" msgstr "你可以调用 :meth:`jwe.encrypt_compact` 来构建一个紧凑型序列化:" #: ../../guide/jwe.rst:60 msgid "" "A compact JWE is constructed by ``protected`` header, ``plaintext`` and a" " public key. In the above example, ``protected`` is the \"protected " "header\" part, `\"hello\"` is the plaintext part, and ``key`` is the " "public key part (oct key is a symmetric key, it is a shared key, there is" " no public or private differences)." msgstr "" #: ../../guide/jwe.rst:65 msgid "" "It is suggested that you learn the :ref:`jwk` section, and find the " "correct key type according to :ref:`JSON Web Encryption Algorithms " "`." msgstr "" "建议您学习 :ref:`jwk` 部分,并根据 :ref:`JSON Web Encryption Algorithms " "` 找到正确的密钥类型。" #: ../../guide/jwe.rst:69 ../../guide/jwe.rst:158 msgid "Decryption" msgstr "解密" #: ../../guide/jwe.rst:71 msgid "" "It is very easy to decrypt the compact serialization in the previous " "example with :meth:`jwe.decrypt_compact`:" msgstr "" #: ../../guide/jwe.rst:82 msgid "" "If the algorithm is accepting an asymmetric key, you MUST use a private " "key in ``decrypt_compact`` method." msgstr "" #: ../../guide/jwe.rst:86 msgid "JSON Encryption" msgstr "" #: ../../guide/jwe.rst:88 msgid "" "The JWE JSON Serialization represents encrypted content as a JSON object." " This representation is neither optimized for compactness nor URL safe." msgstr "" #: ../../guide/jwe.rst:92 msgid "" "An example of a JWE using the general JWE JSON Serialization is as " "follows:" msgstr "" #: ../../guide/jwe.rst:116 msgid "" "``jwe.JSONEncryption`` is separated to ``GeneralJSONEncryption`` and " "``FlattenedJSONEncryption``." msgstr "" #: ../../guide/jwe.rst:118 msgid "" "The structure for JSON JWE serialization is a little complex, developers " "SHOULD create an object of :class:`jwe.GeneralJSONEncryption` at first:" msgstr "" #: ../../guide/jwe.rst:140 msgid "If you prefer adding recipient keys from existing key set:" msgstr "" #: ../../guide/jwe.rst:160 msgid "" "Calling :meth:`jwe.decrypt_json` could decrypt the JSON Serialization in " "the above example. Most of the time, you would need a JWK Set of private " "keys for decryption." msgstr "" #: ../../guide/jwe.rst:177 msgid "" "By default, ``jwe.decrypt_json`` will validate all the recipients, if one" " recipient validation fails, the method will raise an error." msgstr "" #: ../../guide/jwe.rst:180 msgid "" "You can also change the default behavior to bypass the decryption with " "only one recipient get verified:" msgstr "" #: ../../guide/jwe.rst:189 ../../guide/jws.rst:176 msgid "General and Flattened" msgstr "" #: ../../guide/jwe.rst:191 msgid "" "The above example is a General JWE JSON Serialization, there is also a " "Flattened JWE JSON Serialization. The Flattened one MUST ONLY contain one" " recipient." msgstr "" #: ../../guide/jwe.rst:194 msgid "" "The syntax of a JWE using the flattened JWE JSON Serialization is as " "follows:" msgstr "" #: ../../guide/jwe.rst:209 msgid "" "It is flattened, it moves all the members out of the ``recipients`` " "field. To ``encrypt_json`` into a flattened serialization, you can " "construct a :class`jwe.FlattenedJSONEncryption` instead:" msgstr "" #: ../../guide/jwe.rst:217 msgid "And make sure only adding one recipient." msgstr "同时注意,你只能增加一位成员。" #: ../../guide/jwe.rst:220 ../../guide/jwt.rst:290 msgid "Algorithms & Registry" msgstr "算法和注册表" #: ../../guide/jwe.rst:222 msgid "" "``joserfc.jwe`` module would ONLY allow recommended algorithms by " "default, you can find which algorithm is recommended according to " ":ref:`JSON Web Encryption Algorithms `." msgstr "" #: ../../guide/jwe.rst:226 msgid "" "It is possible to support non-recommended algorithms by passing the " "``algorithms`` parameter, or with a custom ``registry``." msgstr "" #: ../../guide/jwe.rst:236 msgid "" "The registry is a little complex, find out more on the :ref:`registry` " "section." msgstr "" #: ../../guide/jwk.rst:11 msgid "" "A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data " "structure that represents a cryptographic key (via RFC7517_)." msgstr "" #: ../../guide/jwk.rst:21 msgid "" "An :class:`OctKey` is a symmetric key defined in `RFC7518 section 6.4 " "`_." msgstr "" #: ../../guide/jwk.rst:25 msgid "Create an \"oct\" key" msgstr "生成一个 OctKey" #: ../../guide/jwk.rst:27 msgid "" "You can generate an ``OctKey`` with the :meth:`OctKey.generate_key` " "method:" msgstr "你可以调用 :meth:`OctKey.generate_key` 来生成一个 ``OctKey``:" #: ../../guide/jwk.rst:37 msgid "Import an \"oct\" key" msgstr "导入一个 OctKey" #: ../../guide/jwk.rst:39 msgid "You can import an ``OctKey`` from string, bytes and a JWK (in dict)." msgstr "您可以从字符串、字节和 JWK(dict)导入一个 ``OctKey``。" #: ../../guide/jwk.rst:52 msgid "When importing a key, you can add extra parameters into a key:" msgstr "" #: ../../guide/jwk.rst:66 msgid "" "An :class:`RSAKey` is an asymmetric key defined in `RFC7518 section 6.3 " "`_. It represents RSA" " keys." msgstr "" #: ../../guide/jwk.rst:71 msgid "Generate an \"RSA\" key" msgstr "生成一个 RSAKey" #: ../../guide/jwk.rst:73 msgid "You can generate an \"EC\" key with a given key size (in bit):" msgstr "" #: ../../guide/jwk.rst:83 msgid "Import an \"RSA\" key" msgstr "导入一个 RSAKey" #: ../../guide/jwk.rst:85 msgid "You can import an ``RSAKey`` from string, bytes and a JWK (in dict)." msgstr "您可以从字符串、字节和 JWK(dict)导入一个 ``RSAKey``。" #: ../../guide/jwk.rst:127 msgid "" "An :class:`ECKey` is an asymmetric key defined in `RFC7518 section 6.2 " "`_. It represents " "Elliptic Curve [DSS] keys." msgstr "" #: ../../guide/jwk.rst:132 msgid "Generate an \"EC\" key" msgstr "生成一个 ECKey" #: ../../guide/jwk.rst:134 msgid "You can generate an \"EC\" key with the given curve:" msgstr "" #: ../../guide/jwk.rst:142 msgid "The \"crv\" values that :class:`ECKey` supports:" msgstr "" #: ../../guide/jwk.rst:144 msgid "``P-256`` via RFC7518" msgstr "RFC7518 的 ``P-256``" #: ../../guide/jwk.rst:145 msgid "``P-384`` via RFC7518" msgstr "RFC7518 的 ``P-384``" #: ../../guide/jwk.rst:146 msgid "``P-521`` via RFC7518" msgstr "RFC7518 的 ``P-521``" #: ../../guide/jwk.rst:147 msgid "``secp256k1`` via RFC8812" msgstr "RFC8812 的 ``secp256k1``" #: ../../guide/jwk.rst:149 msgid "It is ``P-521``, not ``P-512``, it is not a typo." msgstr "这里确实是``P-521``,而不是``P-512``,我们没有写错。" #: ../../guide/jwk.rst:152 msgid "Import an \"EC\" key" msgstr "导入一个 ECKey" #: ../../guide/jwk.rst:154 msgid "You can import an ``ECKey`` from string, bytes and a JWK (in dict)." msgstr "您可以从字符串、字节和 JWK(dict)导入一个 ``ECKey``。" #: ../../guide/jwk.rst:182 msgid "" "An :class:`OKPKey` is an asymmetric key defined in RFC8037_ CFRG Elliptic" " Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and " "Encryption (JOSE)." msgstr "" #: ../../guide/jwk.rst:189 msgid "Generate an \"OKP\" key" msgstr "生成一个 OKPKey" #: ../../guide/jwk.rst:191 msgid "You can generate an \"OKP\" key with the given curve:" msgstr "" #: ../../guide/jwk.rst:199 msgid "" ":class:`OKPKey` accepts \"crv\" values of ``Ed25519``, ``Ed448``, " "``X25519``, and ``X448``." msgstr "" #: ../../guide/jwk.rst:203 msgid "Import an \"OKP\" key" msgstr "导入一个 OKPKey" #: ../../guide/jwk.rst:205 msgid "You can import an ``OKPKey`` from string, bytes and a JWK (in dict)." msgstr "您可以从字符串、字节和 JWK(dict)导入一个 ``OKPKey``。" #: ../../guide/jwk.rst:228 msgid "Key Set" msgstr "" #: ../../guide/jwk.rst:230 msgid "" "A JWK Set is a JSON object that represents a set of JWKs. An example of a" " JWK Set:" msgstr "" #: ../../guide/jwk.rst:254 msgid "Create a key set" msgstr "生成一个 KeySet" #: ../../guide/jwk.rst:256 msgid "You can create a key set with a given set of keys:" msgstr "" #: ../../guide/jwk.rst:264 msgid "Or, you can generate a key set for a certain \"kty\":" msgstr "" #: ../../guide/jwk.rst:271 msgid "Import a key set" msgstr "导入一个 KeySet" #: ../../guide/jwk.rst:273 msgid "An example about importing JWKS from a local file:" msgstr "以下是一个从本地文件导入 JWKS 的例子:" #: ../../guide/jwk.rst:283 msgid "An example about importing JWKS from a URL:" msgstr "" #: ../../guide/jwk.rst:293 msgid "Key methods" msgstr "" #: ../../guide/jwk.rst:298 msgid "``thumbprint``" msgstr "" #: ../../guide/jwk.rst:300 msgid "" "Call this method will generate the thumbprint with algorithm defined in " "RFC7638." msgstr "" #: ../../guide/jwk.rst:312 msgid "``ensure_kid``" msgstr "" #: ../../guide/jwk.rst:314 msgid "" "Call this method to make sure the key contains a ``kid``. If the key has " "no ``kid``, generate one with the above ``.thumbprint`` method." msgstr "" #: ../../guide/jwk.rst:328 msgid "``as_dict``" msgstr "" #: ../../guide/jwk.rst:330 msgid "" "Dump a key or key set into dict format, which can be used to convert to " "JSON:" msgstr "" #: ../../guide/jwk.rst:340 msgid "``as_pem``" msgstr "" #: ../../guide/jwk.rst:342 msgid "Dump an asymmetric key into PEM format (in bytes):" msgstr "将非对称密钥转换为 PEM 格式(数据为字节形式):" #: ../../guide/jwk.rst:353 msgid "``as_der``" msgstr "" #: ../../guide/jwk.rst:355 msgid "Dump an asymmetric key into DER format (in bytes):" msgstr "" #: ../../guide/jwk.rst:366 msgid "``JWKRegistry``" msgstr "" #: ../../guide/jwk.rst:368 msgid "" "The :class:`JWKRegistry` class serves as a registry for storing all the " "supported key types in the ``joserfc`` library. While developers " "typically use specific key types such as ``RSAKey`` or ``ECKey``, this " "registry offers a means to dynamically import and generate keys." msgstr "" #: ../../guide/jwk.rst:374 msgid "Import keys" msgstr "" #: ../../guide/jwk.rst:376 msgid "" "The :meth:`JWKRegistry.import_key` can choose the correct key type " "automatically when importing a JWK in dict:" msgstr "" #: ../../guide/jwk.rst:393 msgid "" "If the key is in bytes or string, not dict, developers SHOULD specify the" " key type manually:" msgstr "" #: ../../guide/jwk.rst:403 msgid "You can use ``jwk.import_key`` directly. For example::" msgstr "" #: ../../guide/jwk.rst:410 msgid "Generate keys" msgstr "" #: ../../guide/jwk.rst:412 msgid "" "The :meth:`JWKRegistry.generate_key` can generate a key with all the " "supported key types. For ``oct`` and ``RSA`` the parameters in this " "method:" msgstr "" #: ../../guide/jwk.rst:422 msgid "For ``EC`` and ``OKP`` keys, the parameters are:" msgstr "" #: ../../guide/jwk.rst:432 msgid "You can use ``jwk.generate_key`` directly. For example::" msgstr "" #: ../../guide/jwk.rst:438 msgid "Options" msgstr "" #: ../../guide/jwk.rst:440 msgid "" "The ``import_key`` and ``generate_key`` methods available in ``OctKey``, " "``RSAKey``, ``ECKey``, ``OKPKey``, and ``JWKRegistry`` classes have an " "optional ``parameters`` parameter. This ``parameters`` allows you to " "provide a dict that includes additional key parameters to be included in " "the JWK." msgstr "" #: ../../guide/jwk.rst:445 msgid "Some of the standard (registered) header fields are:" msgstr "" #: ../../guide/jwk.rst:447 msgid "``kty``: Key Type, it is automatically added" msgstr "" #: ../../guide/jwk.rst:448 msgid "``use``: Public Key Use, \"sig\" or \"enc\"" msgstr "" #: ../../guide/jwk.rst:449 msgid "``key_ops``: Key Operations, allowed operations of this key" msgstr "" #: ../../guide/jwk.rst:450 msgid "``alg``: Algorithm, allowed algorithm of this key" msgstr "" #: ../../guide/jwk.rst:451 msgid "``kid``: Key ID, a string of the key ID" msgstr "" #: ../../guide/jwk.rst:453 msgid "" "When using ``import_key`` and ``generate_key``, developers can pass the " "extra key ``parameters``:" msgstr "" #: ../../guide/jwk.rst:460 msgid "" "The above ``RSAKey`` then can only be used for ``JWS`` with ``alg`` of " "``RS256``, and it can only be used for deserialization (``verify``)." msgstr "" #: ../../guide/jws.rst:11 msgid "" "JSON Web Signature (JWS) represents content secured with digital " "signatures or Message Authentication Codes (MACs) using JSON-based data " "structures. (via RFC7515_)" msgstr "" #: ../../guide/jws.rst:18 msgid "Compact Signature" msgstr "" #: ../../guide/jws.rst:20 msgid "" "The JWS Compact Serialization represents digitally signed or MACed " "content as a compact, URL-safe string. This string is:" msgstr "" #: ../../guide/jws.rst:39 ../../guide/jws.rst:102 msgid "Serialization" msgstr "" #: ../../guide/jws.rst:41 msgid "" "You can call :meth:`jws.serialize_compact` to construct a compact JWS " "serialization:" msgstr "" #: ../../guide/jws.rst:53 msgid "" "A compact JWS is constructed by protected header, payload and a private " "key. In the above example, ``protected`` is the \"protected header\" " "part, `\"hello\"` is the payload part, and `\"secret\"` is a plain " "private key." msgstr "" #: ../../guide/jws.rst:58 ../../guide/jws.rst:159 msgid "Deserialization" msgstr "" #: ../../guide/jws.rst:60 msgid "" "Calling :meth:`jws.deserialize_compact` to extract and verify the compact" " serialization with a public key." msgstr "" #: ../../guide/jws.rst:75 msgid "JSON Signature" msgstr "" #: ../../guide/jws.rst:77 msgid "" "The JWS JSON Serialization represents digitally signed or MACed content " "as a JSON object. This representation is neither optimized for " "compactness nor URL-safe." msgstr "" #: ../../guide/jws.rst:81 msgid "An example of a JSON serialization:" msgstr "" #: ../../guide/jws.rst:104 msgid "" "You can call :meth:`jws.serialize_json` to construct a JSON JWS " "serialization:" msgstr "" #: ../../guide/jws.rst:133 msgid "" "The JSON JWS serialization is constructed by members, payload and private" " key. A **member** is a combination of protected header and public " "header:" msgstr "" #: ../../guide/jws.rst:143 msgid "" "The ``protected`` header will be base64 encoded in the JSON " "serialization, together with the payload to sign a signature for the " "member:" msgstr "" #: ../../guide/jws.rst:155 msgid "" "In the above example, we passed a :class:`jwk.KeySet` as the private key " "parameter, the :meth:`jws.serialize_json` will find the correct key in " "the key set by ``kid``." msgstr "" #: ../../guide/jws.rst:161 msgid "" "Calling :meth:`jws.deserialize_json` to extract and verify the JSON " "serialization with a public key." msgstr "" #: ../../guide/jws.rst:178 msgid "" "There are two types of JSON JWS serializations, \"general\" and " "\"flattened\". The above example is a General JSON Serialization. A " "Flattened JSON Serialization contains only one member. Compare the below " "examples:" msgstr "" #: ../../guide/jws.rst:182 msgid "Flattened JSON Serialization" msgstr "" #: ../../guide/jws.rst:192 msgid "General JSON Serialization" msgstr "" #: ../../guide/jws.rst:206 msgid "" "You can pass a member dict to construct a flattened serialization; and a " "list of members to construct a general serialization:" msgstr "" #: ../../guide/jws.rst:222 msgid "" "The returned value from ``deserialize_json`` is an object of " ":class:`jws.GeneralJSONSignature` or :class:`jws.FlattenedJSONSignature`," " you can tell if the signature is flattened or general with " "``obj.flattened``:" msgstr "" #: ../../guide/jws.rst:228 msgid "" "``jws.JSONSignature`` is separated to ``GeneralJSONSignature`` and " "``FlattenedJSONSignature``." msgstr "" #: ../../guide/jws.rst:241 msgid "" "``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, and " "RFC8812. Here lists all the algorithms ``joserfc.jws`` supporting:" msgstr "" #: ../../guide/jws.rst:245 msgid "Description" msgstr "" #: ../../guide/jws.rst:247 msgid "No digital signature or MAC performed" msgstr "" #: ../../guide/jws.rst:248 msgid "HMAC using SHA-256" msgstr "" #: ../../guide/jws.rst:248 ../../guide/jws.rst:251 ../../guide/jws.rst:254 msgid ":bdg-success:`YES`" msgstr "" #: ../../guide/jws.rst:249 msgid "HMAC using SHA-384" msgstr "" #: ../../guide/jws.rst:250 msgid "HMAC using SHA-512" msgstr "" #: ../../guide/jws.rst:251 msgid "RSASSA-PKCS1-v1_5 using SHA-256" msgstr "" #: ../../guide/jws.rst:252 msgid "RSASSA-PKCS1-v1_5 using SHA-384" msgstr "" #: ../../guide/jws.rst:253 msgid "RSASSA-PKCS1-v1_5 using SHA-512" msgstr "" #: ../../guide/jws.rst:254 msgid "ECDSA using P-256 and SHA-256" msgstr "" #: ../../guide/jws.rst:255 msgid "ECDSA using P-384 and SHA-384" msgstr "" #: ../../guide/jws.rst:256 msgid "ECDSA using P-521 and SHA-512" msgstr "" #: ../../guide/jws.rst:257 msgid "RSASSA-PSS using SHA-256 and MGF1 with SHA-256" msgstr "" #: ../../guide/jws.rst:258 msgid "RSASSA-PSS using SHA-384 and MGF1 with SHA-384" msgstr "" #: ../../guide/jws.rst:259 msgid "RSASSA-PSS using SHA-512 and MGF1 with SHA-512" msgstr "" #: ../../guide/jws.rst:260 msgid "Edwards-curve Digital Signature" msgstr "" #: ../../guide/jws.rst:261 msgid "ECDSA using secp256k1 curve and SHA-256" msgstr "" #: ../../guide/jws.rst:265 msgid "UnsupportedAlgorithmError" msgstr "" #: ../../guide/jws.rst:269 msgid "" "From version 1.1.0, an ``UnsupportedAlgorithmError`` will be raised " "instead of a ``ValueError``." msgstr "" #: ../../guide/jws.rst:272 msgid "" "The serialization and deserialization methods on ``joserfc.jws`` module " "accept an ``algorithms`` parameter for specifying the allowed algorithms." " By default, those ``serialize`` and ``deserialize`` methods will ONLY " "allow recommended algorithms defined by RFCs. With non recommended " "algorithms, you may encounter the below error." msgstr "" #: ../../guide/jws.rst:293 msgid "" "``joserfc`` does support ``HS384``, but this algorithm is not recommended" " by specifications, developers MUST explicitly specify the supported " "algorithms either by the ``algorithms`` parameter, or ``registry`` " "parameter." msgstr "" #: ../../guide/jws.rst:305 msgid "" "Developers can also apply the ``registry`` parameter to resolve this " "issue. Here is an example of using :ref:`registry`." msgstr "" #: ../../guide/jws.rst:320 msgid "Unencoded Payload Option" msgstr "" #: ../../guide/jws.rst:322 msgid "" "The unencoded payload option, defined in RFC7797, allows the payload of a" " JWS (JSON Web Signature) to remain unencoded, without using base64 " "encoding." msgstr "" #: ../../guide/jws.rst:325 msgid "" "To enable this option, you need to set the ``b64`` header parameter to " "``false`` in the JWS header." msgstr "" #: ../../guide/jws.rst:328 msgid "" "To utilize the unencoded payload option in joserfc, you must import the " "serialize and deserialize methods from ``joserfc.rfc7797``." msgstr "" #: ../../guide/jws.rst:331 msgid "Here are examples demonstrating the usage of the ``b64`` option:" msgstr "" #: ../../guide/jws.rst:346 msgid "" "The ``crit`` MUST be present with ``\"b64\"`` in its value set when " "``b64`` is in the header." msgstr "" #: ../../guide/jws.rst:349 msgid "" "Since the payload is not base64 encoded, if the payload contains non " "urlsafe characters, the compact serialization will detach the payload:" msgstr "" #: ../../guide/jws.rst:365 msgid "" "There are also methods for JSON serialization: ``serialize_json`` and " "``deserialize_json``." msgstr "" #: ../../guide/jwt.rst:11 msgid "" "JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe` and " "includes specific payload claims. These claims are required to be in JSON" " format and follow a predefined set of fields." msgstr "" #: ../../guide/jwt.rst:17 msgid "" "Do you know that JSON Web Token (JWT) is not a part of JOSE. Instead, it " "was created by the OAuth working group." msgstr "" #: ../../guide/jwt.rst:21 msgid "Encode token" msgstr "" #: ../../guide/jwt.rst:23 msgid "" ":meth:`encode` is the method for creating a JSON Web Token string. It " "encodes the payload with the given ``alg`` in header:" msgstr "" #: ../../guide/jwt.rst:36 msgid "The returned value of ``text`` in above example is:" msgstr "" #: ../../guide/jwt.rst:44 msgid "Line breaks for display only." msgstr "" #: ../../guide/jwt.rst:47 msgid "Decode token" msgstr "" #: ../../guide/jwt.rst:49 msgid "" ":meth:`decode` is the method to translate a JSON Web Token string into a " "token object which contains ``.header`` and ``.claims`` properties:" msgstr "" #: ../../guide/jwt.rst:62 msgid "Validate claims" msgstr "" #: ../../guide/jwt.rst:64 msgid "" "The ``jwt.decode`` method will only verify if the payload is a JSON " "base64 string." msgstr "" #: ../../guide/jwt.rst:67 msgid "" "You can define claims requests :class:`JWTClaimsRegistry` for validating " "the decoded claims. The ``JWTClaimsRegistry`` accepts each claim as an " "`Individual Claims Requests `_ JSON object." msgstr "" #: ../../guide/jwt.rst:83 msgid "The Individual Claims Requests JSON object contains:" msgstr "" #: ../../guide/jwt.rst:85 msgid "``essential``" msgstr "" #: ../../guide/jwt.rst:86 msgid "" "OPTIONAL. Indicates whether the Claim being requested is an Essential " "Claim. If the value is true, this indicates that the Claim is an " "Essential Claim." msgstr "" #: ../../guide/jwt.rst:89 msgid "``value``" msgstr "" #: ../../guide/jwt.rst:90 msgid "OPTIONAL. Requests that the Claim be returned with a particular value." msgstr "" #: ../../guide/jwt.rst:92 msgid "``values``" msgstr "" #: ../../guide/jwt.rst:93 msgid "" "OPTIONAL. Requests that the Claim be returned with one of a set of " "values, with the values appearing in order of preference." msgstr "" #: ../../guide/jwt.rst:96 msgid "And we added one more field:" msgstr "" #: ../../guide/jwt.rst:98 msgid "``allow_blank``" msgstr "" #: ../../guide/jwt.rst:99 msgid "OPTIONAL. Allow essential claims to be an empty string." msgstr "" #: ../../guide/jwt.rst:102 msgid "Missing essential claims" msgstr "" #: ../../guide/jwt.rst:117 msgid "Allow empty essential claims" msgstr "" #: ../../guide/jwt.rst:128 msgid "Invalid claims values" msgstr "" #: ../../guide/jwt.rst:138 msgid "Default validators" msgstr "" #: ../../guide/jwt.rst:140 msgid "" "The ``JWTClaimsRegistry`` has built-in validators for timing related " "fields:" msgstr "" #: ../../guide/jwt.rst:142 msgid "``exp``: expiration time" msgstr "" #: ../../guide/jwt.rst:143 msgid "``nbf``: not before" msgstr "" #: ../../guide/jwt.rst:144 msgid "``iat``: issued at" msgstr "" #: ../../guide/jwt.rst:147 msgid "JWS & JWE" msgstr "" #: ../../guide/jwt.rst:149 msgid "" "JWT is built on top of JWS and JWE, all of the above examples are in JWS." " By default ``jwt.encode`` and ``jwt.decode`` work for **JWS**. To use " "**JWE**, you need to specify a ``registry`` parameter with " "``JWERegistry``. Here is an example of JWE:" msgstr "" #: ../../guide/jwt.rst:164 msgid "" "The JWE formatted result contains 5 parts, while JWS only contains 3 " "parts, a JWE example would be something like this (line breaks for " "display only):" msgstr "" #: ../../guide/jwt.rst:175 msgid "Another difference is the key used for ``encode`` and ``decode``." msgstr "" #: ../../guide/jwt.rst:177 msgid "" "For :ref:`jws`, a private key is used for ``encode``, and a public key is" " used for ``decode``. The ``encode`` method will use a private key to " "sign, and the ``decode`` method will use a public key to verify." msgstr "" #: ../../guide/jwt.rst:181 msgid "" "For :ref:`jwe`, it is the contrary, a public key is used for ``encode``, " "and a private key is used for ``decode``. The ``encode`` method will use" " a public key to encrypt, and the ``decode`` method will use a private " "key to decrypt." msgstr "" #: ../../guide/jwt.rst:186 msgid "The key parameter" msgstr "" #: ../../guide/jwt.rst:188 msgid "" "In the above example, we're using :ref:`OctKey` only for simplicity. " "There are other types of keys in :ref:`jwk`." msgstr "" #: ../../guide/jwt.rst:192 msgid "Key types" msgstr "" #: ../../guide/jwt.rst:194 msgid "" "Each algorithm (``alg`` in header) requires a certain type of key. For " "example:" msgstr "" #: ../../guide/jwt.rst:196 msgid "``HS256`` requires ``OctKey``" msgstr "" #: ../../guide/jwt.rst:197 msgid "``RS256`` requires ``RSAKey``" msgstr "" #: ../../guide/jwt.rst:198 msgid "``ES256`` requires ``ECKey`` or ``OKPKey``" msgstr "" #: ../../guide/jwt.rst:200 msgid "You can find the correct key type for each algorithm at:" msgstr "" #: ../../guide/jwt.rst:202 ../../guide/jwt.rst:298 msgid ":ref:`JSON Web Signature Algorithms `" msgstr "" #: ../../guide/jwt.rst:203 ../../guide/jwt.rst:299 msgid ":ref:`JSON Web Encryption Algorithms `" msgstr "" #: ../../guide/jwt.rst:205 msgid "Here is an example of a JWT with \"alg\" of ``RS256`` in JWS type:" msgstr "" #: ../../guide/jwt.rst:224 msgid "" "In production, ``jwt.encode`` is usually used by the *client* side, a " "client normally does not have the access to private keys. The server " "provider would usually expose the public keys in JWK Set." msgstr "" #: ../../guide/jwt.rst:229 msgid "Use key set" msgstr "" #: ../../guide/jwt.rst:231 msgid "" "You can also pass a JWK Set to the ``key`` parameter of :meth:`encode` " "and :meth:`decode` methods." msgstr "" #: ../../guide/jwt.rst:248 msgid "" "The methods will find the correct key according to the ``kid`` you " "specified. If there is no ``kid`` in header, it will pick on randomly and" " add the ``kid`` of the key into header." msgstr "" #: ../../guide/jwt.rst:252 msgid "" "A client would usually get the public key set from a public URL, normally" " the ``decode`` code would be something like:" msgstr "" #: ../../guide/jwt.rst:269 msgid "Callable key" msgstr "" #: ../../guide/jwt.rst:271 msgid "It is also possible to assign a callable function as the ``key``:" msgstr "" #: ../../guide/jwt.rst:292 msgid "" "The :meth:`encode` and :meth:`decode` accept an ``algorithms`` parameter " "for specifying the allowed algorithms. By default, it only allows your to" " use **recommended** algorithms." msgstr "" #: ../../guide/jwt.rst:296 msgid "You can find out the recommended algorithms at:" msgstr "" #: ../../guide/jwt.rst:301 msgid "" "For instance, ``HS386`` is not a recommended algorithm, and you want to " "use this algorithm:" msgstr "" #: ../../guide/jwt.rst:312 msgid "" "If not specifying the ``algorithms`` parameter, the ``encode`` method " "will raise an error." msgstr "" #: ../../guide/jwt.rst:316 #, fuzzy msgid "JSON Encoder and Decoder" msgstr "JWT 的编码与解码" #: ../../guide/jwt.rst:320 msgid "" "The parameters ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` for" " ``jwt.decode`` were introduced in version 1.1.0." msgstr "" #: ../../guide/jwt.rst:323 msgid "" "When using ``jwt.encode``` to encode claims that contain data types that " "``json`` module does not natively support, such as ``UUID`` and " "``datetime``, an error will be raised." msgstr "" #: ../../guide/jwt.rst:356 msgid "" "To resolve this issue, you can pass a custom ``JSONEncoder`` using the " "``encoder_cls`` parameter." msgstr "" #: ../../guide/jwt.rst:374 msgid "" "Additionally, ``jwt.decode`` accepts a ``decoder_cls`` parameter. If you " "need to convert the decoded claims into the appropriate data types, you " "can provide a custom decoder class." msgstr "" #: ../../guide/registry.rst:6 msgid "Registry" msgstr "注册表" #: ../../guide/registry.rst:11 msgid "" "The ``registry`` is specifically designed to store supported algorithms, " "allowed algorithms, registered header parameters, and provides methods to" " validate algorithms and headers." msgstr "" #: ../../guide/registry.rst:17 msgid "" "We'll use ``JWSRegistry`` as our reference, but keep in mind that the " "behavior of ``JWERegistry`` is identical." msgstr "" #: ../../guide/registry.rst:23 msgid "" "The ``JWSRegistry`` or ``JWERegistry`` serves as a storage for all " "supported algorithms in JWS or JWE. By default, it enforces the usage of " "recommended algorithms, ensuring a higher level of security." msgstr "" #: ../../guide/registry.rst:27 msgid "Find all the supported and recommended algorithms in:" msgstr "" #: ../../guide/registry.rst:29 msgid ":ref:`jws_algorithms`" msgstr "" #: ../../guide/registry.rst:30 msgid ":ref:`jwe_algorithms`" msgstr "" #: ../../guide/registry.rst:32 msgid "" "You have the flexibility to create a custom registry tailored to your " "specific program requirements, allowing you to define and restrict the " "algorithms used. For instance, you can design a custom JWS registry that " "only permits the usage of ``RS256`` and ``ES256`` algorithms. This " "ensures that only these specific algorithms are allowed in your program." msgstr "" #: ../../guide/registry.rst:45 #, python-brace-format msgid "" "An example of a custom JWE registry that only permits the usage of " "``{\"alg\": \"A128KW\", \"enc\": \"A128GCM\"}``:" msgstr "" #: ../../guide/registry.rst:56 msgid "Headers" msgstr "" #: ../../guide/registry.rst:58 msgid "" "By default, the ``JWSRegistry`` only permits the usage of registered " "header parameters. Additionally, it verifies the validity of the header " "parameter values before allowing their usage." msgstr "" #: ../../guide/registry.rst:63 msgid "Type checking" msgstr "类型检查" #: ../../guide/registry.rst:65 msgid "" "The header parameter registry for JWS and JWE performs an initial check " "on the value type." msgstr "" #: ../../guide/registry.rst:84 msgid "" "In the above example, ``kid`` MUST be a string instead of an integer. The" " default registry validates the ``kid`` before processing the " "serialization." msgstr "" #: ../../guide/registry.rst:88 msgid "Critical headers" msgstr "" #: ../../guide/registry.rst:90 msgid "" "There is a special \"crit\" header parameter for JWS and JWE, which " "specifies the critical header parameters. These critical parameters are " "considered mandatory, indicating that they must be present. For example:" msgstr "" #: ../../guide/registry.rst:110 msgid "" "Since \"kid\" is listed as a critical (``crit``) header parameter, it is " "mandatory and must be included in the header." msgstr "" #: ../../guide/registry.rst:114 msgid "Additional headers" msgstr "" #: ../../guide/registry.rst:116 msgid "" "By default, the registry for JWS and JWE only permits registered header " "parameters. Any additional header beyond those supported by the algorithm" " will result in an error." msgstr "" #: ../../guide/registry.rst:135 msgid "" "To resolve this error, you have two options. First, you can register the " "additional header parameters with the registry. This allows the registry " "to recognize and validate those parameters instead of raising an error." msgstr "" #: ../../guide/registry.rst:159 msgid "" "Alternatively, you can choose to disable the strict header checking " "altogether. By turning off strict header checking, the registry will no " "longer raise an error for unrecognized header parameters. However, please" " note that this approach may compromise the security and integrity of the" " token, so it should be used with caution." msgstr "" #: ../../guide/registry.rst:171 msgid "Registry for JWT" msgstr "" #: ../../guide/registry.rst:173 msgid "" "JSON Web Token (JWT) is built on top of :ref:`jws` or :ref:`jwe`. The " "``encode`` and ``decode`` methods accept a ``registry`` parameter. " "Depending on the algorithm of the JWT, you need to decide whether to use " "``JWSRegistry`` or ``JWERegistry``." msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/index.po000066400000000000000000000111321501424510600213670ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-15 13:31+0900\n" "PO-Revision-Date: 2023-07-15 14:44+0900\n" "Last-Translator: Hsiaoming Yang \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" #: ../../index.rst:67 msgid "Getting started" msgstr "开始上手" #: ../../index.rst:74 msgid "Essentials" msgstr "必读文档" #: ../../index.rst:83 msgid "Recipes" msgstr "小技巧" #: ../../index.rst:91 msgid "Development" msgstr "开发者文档" #: ../../index.rst:2 msgid "JOSE RFC" msgstr "JOSE RFC" #: ../../index.rst:4 msgid "" "``joserfc`` is a Python library that provides a comprehensive " "implementation of several essential JSON Object Signing and Encryption " "(JOSE) standards, including JWS (JSON Web Signature), JWE (JSON Web " "Encryption), JWK (JSON Web Key), JWA (JSON Web Algorithms), and JWT (JSON" " Web Tokens)." msgstr "" "``joserfc`` 是一个提供了多个重要 JSON Object Signing and Encryption (JOSE) 标准的全面实现的" " Python 库,包括 JWS (JSON Web Signature),JWE (JSON Web Encryption),JWK (JSON" " Web Key),JWA (JSON Web Algorithms),和 JWT (JSON Web Tokens)。" #: ../../index.rst:9 msgid "" "It is derived from Authlib_, but features a redesigned API specific to " "JOSE functionality." msgstr "它源于 Authlib_,但我们专门为 JOSE 功能重新设计了 API。" #: ../../index.rst:14 msgid "Usage" msgstr "使用方法" #: ../../index.rst:16 msgid "" "A quick and simple JWT encoding and decoding would look something like " "this:" msgstr "一个快速而简单的 JWT 编码和解码的示例如下:" #: ../../index.rst:32 msgid "You would find more details and advanced usage in :ref:`jwt` section." msgstr "您可以在 :ref:`jwt` 部分找到更多详细信息和高级用法。" #: ../../index.rst:36 msgid "" "The string ``\"secret\"`` employed in the above example is solely " "intended for demonstration purposes. In a production environment, it is " "crucial to use a highly secure secret key to ensure robust security " "measures." msgstr "" "上述示例中使用的字符串 ``\"secret\"`` 仅用于演示目的。在生产环境中," "使用高度安全的密钥以确保强大的安全措施是至关重要的。" #: ../../index.rst:41 msgid "RFCs" msgstr "RFCs" #: ../../index.rst:43 msgid "It follows RFCs with extensible API. The module has implementations of:" msgstr "它遵循可扩展的 API,并遵循 RFC 标准。该模块包含以下实现:" #: ../../index.rst:45 msgid "RFC7515: :ref:`JSON Web Signature `" msgstr "RFC7515: :ref:`JSON Web Signature `" #: ../../index.rst:46 msgid "RFC7516: :ref:`JSON Web Encryption `" msgstr "RFC7516: :ref:`JSON Web Encryption `" #: ../../index.rst:47 msgid "RFC7517: :ref:`JSON Web Key `" msgstr "RFC7517: :ref:`JSON Web Key `" #: ../../index.rst:48 msgid "RFC7518: :ref:`JSON Web Algorithms `" msgstr "RFC7518: :ref:`JSON Web Algorithms `" #: ../../index.rst:49 msgid "RFC7519: :ref:`JSON Web Token `" msgstr "RFC7519: :ref:`JSON Web Token `" #: ../../index.rst:50 msgid "" "RFC7520: Examples of Protecting Content Using JSON Object Signing and " "Encryption" msgstr "" "RFC7520: 使用 JSON 对象签名和加密保护内容的示例" #: ../../index.rst:51 msgid "RFC7638: ``thumbprint`` for JWK" msgstr "RFC7638: JWK 的 ``thumbprint``" #: ../../index.rst:52 msgid "RFC8037: :ref:`OKPKey` and ``EdDSA`` algorithm" msgstr "RFC8037: :ref:`OKPKey` 和 ``EdDSA`` 算法" #: ../../index.rst:53 msgid "RFC8812: ``ES256K`` algorithm" msgstr "RFC8812: ``ES256K`` 算法" #: ../../index.rst:55 msgid "And draft RFCs implementation of:" msgstr "同时包含如下 JOSE 草案的实现:" #: ../../index.rst:57 msgid ":ref:`chacha20`" msgstr ":ref:`chacha20`" #: ../../index.rst:58 msgid ":ref:`ecdh1pu`" msgstr ":ref:`ecdh1pu`" #: ../../index.rst:60 msgid "RFC7520 is implemented as test cases." msgstr "RFC7520 是测试案例,详情请参考源码里的 tests 部分。" #: ../../index.rst:63 msgid "Next" msgstr "继续阅读" #: ../../index.rst:65 msgid "" "Explore the following sections to discover more about ``joserfc`` and its" " features." msgstr "浏览以下部分,了解更多关于 ``joserfc`` 及其特性的内容。" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/install.po000066400000000000000000000063641501424510600217410ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-02-28 12:33+0900\n" "PO-Revision-Date: 2023-07-15 14:44+0900\n" "Last-Translator: Hsiaoming Yang \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../install.rst:4 msgid "Installation" msgstr "安装" #: ../../install.rst:8 msgid "Get started with **joserfc** from installation." msgstr "从安装开始使用 **joserfc**。" #: ../../install.rst:12 msgid "" "We recommend using the latest version of Python. ``joserfc`` supports " "Python 3.8 and newer. The package has a dependency of cryptography_, if " "you encountered any issues related with cryptography, you can follow the " "documentation `installation of cryptography " "`_." msgstr "" "我们建议使用最新版本的 Python。``joserfc`` 支持 Python 3.8 及更高版本。该库依赖 " "cryptography_,如果您遇到与 cryptography 相关的任何问题,您可以参考文档 `cryptography 的安装指南 " "`_。" #: ../../install.rst:20 msgid "pip install" msgstr "pip 安装" #: ../../install.rst:22 msgid "" "``joserfc`` is conveniently available as a Python package on PyPI and can" " be easily installed using pip." msgstr "``joserfc`` 是一个 Python 库,您可以方便地在 PyPI 上获取,并可使用 pip 进行安装。" #: ../../install.rst msgid "General" msgstr "正常安装" #: ../../install.rst msgid "C20P and XC20P" msgstr "C20P 与 XC20P" #: ../../install.rst:41 msgid "" "To use :ref:`chacha20` algorithms, developers have to install the " "``PyCryptodome`` module." msgstr "要使用 :ref:`chacha20` 算法,开发人员必须安装 ``PyCryptodome`` 模块。" #: ../../install.rst:44 msgid "Dependency management" msgstr "依赖管理" #: ../../install.rst:46 msgid "" "There are several ways to manage the dependencies of your project, here " "are some examples to track ``joserfc`` in your project." msgstr "有多种方法可以管理项目的依赖关系,以下是一些示例,可以用来跟踪您项目中的 ``joserfc``。" #: ../../install.rst:50 ../../install.rst:55 msgid "pyproject.toml" msgstr "pyproject.toml" #: ../../install.rst:52 msgid "" "If you're using ``pyproject.toml`` for your Python project, you can add " "``joserfc`` to ``project.dependencies``." msgstr "" "如果您的 Python 项目使用 ``pyproject.toml``,您可以将 ``joserfc`` 添加到 " "``project.dependencies`` 中。" #: ../../install.rst:64 ../../install.rst:69 msgid "requirements.txt" msgstr "requirements.txt" #: ../../install.rst:66 msgid "" "If you're tracking dependencies in ``requirements.txt``, you can add " "``joserfc`` to the requirements file." msgstr "" "如果您在 ``requirements.txt`` 中跟踪依赖项,您可以将 ``joserfc`` 添加到 " "``requirements.txt`` 文件中。" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/migrations.po000066400000000000000000000257201501424510600224440ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-02-28 11:54+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../migrations/authlib.rst:2 msgid "Migrating from Authlib" msgstr "从 Authlib 迁移到 joserfc" #: ../../migrations/authlib.rst:4 msgid "" "``joserfc`` is derived from Authlib and shares similar implementations of" " algorithms. However, it is important to note that the APIs are different" " between the two libraries. When migrating your code from Authlib to " "``joserfc``, you will need to update your code to accommodate the new API" " structure and functionality." msgstr "" #: ../../migrations/authlib.rst:11 ../../migrations/python-jose.rst:85 msgid "JWT" msgstr "" #: ../../migrations/authlib.rst:13 msgid "" "Migrating JWT (JSON Web Token) operations from Authlib to ``joserfc`` " "involves some considerations regarding security design and the allowed " "algorithms." msgstr "" #: ../../migrations/authlib.rst:17 ../../migrations/pyjwt.rst:11 msgid "jwt.encode" msgstr "" #: ../../migrations/authlib.rst:19 msgid "" "The interface for JWT operations in both ``authlib.jose`` and ``joserfc``" " is quite similar. In both libraries, you can encode a JWT using the " "``jwt.encode(header, payload, key)`` method." msgstr "" #: ../../migrations/authlib.rst:22 ../../migrations/authlib.rst:80 msgid "Authlib" msgstr "" #: ../../migrations/authlib.rst:30 ../../migrations/authlib.rst:92 #: ../../migrations/pyjwt.rst:23 ../../migrations/pyjwt.rst:45 #: ../../migrations/pyjwt.rst:77 ../../migrations/pyjwt.rst:117 #: ../../migrations/pyjwt.rst:136 ../../migrations/python-jose.rst:31 #: ../../migrations/python-jose.rst:72 ../../migrations/python-jose.rst:101 #: ../../migrations/python-jose.rst:122 msgid "joserfc" msgstr "" #: ../../migrations/authlib.rst:40 ../../migrations/pyjwt.rst:34 msgid "jwt.decode" msgstr "" #: ../../migrations/authlib.rst:42 msgid "" "The ``jwt.decode`` method in Authlib and ``joserfc`` behaves differently " "when it comes to claims validation." msgstr "" #: ../../migrations/authlib.rst:45 msgid "" "In Authlib, the ``jwt.decode`` method combines the decoding of the JWT " "and the validation of its claims into a single step." msgstr "" #: ../../migrations/authlib.rst:56 msgid "" "In ``joserfc``, the ``jwt.decode`` process is split into two steps: " "decoding the token and then separately validating its claims. This " "approach provides more flexibility and allows for granular control over " "the validation process." msgstr "" #: ../../migrations/authlib.rst:72 msgid "" "You can learn more about :ref:`claims validation ` on the " ":ref:`jwt` guide." msgstr "" #: ../../migrations/authlib.rst:75 ../../migrations/python-jose.rst:14 msgid "JWS" msgstr "" #: ../../migrations/authlib.rst:77 msgid "" "When migrating JWS (JSON Web Signature) operations from Authlib to " "``joserfc``, follow these steps:" msgstr "" #: ../../migrations/authlib.rst:104 msgid "" "Above is a simple example of using the ``HS256`` algorithm for JWS. If " "you would like to explore further and learn more about JWS, we recommend " "referring to the comprehensive :ref:`jws` guide." msgstr "" #: ../../migrations/authlib.rst:109 ../../migrations/python-jose.rst:55 msgid "JWE" msgstr "" #: ../../migrations/authlib.rst:111 msgid "" "The method names for JWE serialization and deserialization are different " "between Authlib and ``joserfc``." msgstr "" #: ../../migrations/authlib.rst:114 msgid "In Authlib, the methods for JWE serialization and deserialization are:" msgstr "" #: ../../migrations/authlib.rst:116 msgid "``.serialize_compact(header, payload, key)``" msgstr "" #: ../../migrations/authlib.rst:117 msgid "``.deserialize_compact(token, key)``" msgstr "" #: ../../migrations/authlib.rst:127 msgid "" "In ``joserfc``, the equivalent methods for JWE serialization and " "deserialization are:" msgstr "" #: ../../migrations/authlib.rst:129 msgid "``.encrypt_compact(header, payload, key)``" msgstr "" #: ../../migrations/authlib.rst:130 msgid "``.decrypt_compact(token, key)``" msgstr "" #: ../../migrations/authlib.rst:139 msgid "" "If you would like to explore further and learn more about JWS, we " "recommend referring to the comprehensive :ref:`jwe` guide." msgstr "" #: ../../migrations/index.rst:2 msgid "Migrations" msgstr "迁移" #: ../../migrations/index.rst:4 msgid "" "Here are some migration guides to help you transition from other " "libraries to ``joserfc``:" msgstr "" #: ../../migrations/pyjwt.rst:2 msgid "Migrating from PyJWT" msgstr "从 PyJWT 迁移到 joserfc" #: ../../migrations/pyjwt.rst:4 msgid "" "When migrating from PyJWT to ``joserfc``, there are a few key differences" " to be aware of. ``joserfc`` provides implementations for JWS (JSON Web " "Signature), JWE (JSON Web Encryption), JWK (JSON Web Key), and JWT (JSON " "Web Token), whereas PyJWT focuses primarily on JWS and JWT. Additionally," " joserfc supports both JWT on JWS and JWT on JWE, offering more " "flexibility for token handling." msgstr "" #: ../../migrations/pyjwt.rst:13 msgid "" "Both PyJWT and joserfc use the ``.encode`` method to generate a JWT, but " "the parameter structure differs between the two libraries." msgstr "" #: ../../migrations/pyjwt.rst:16 ../../migrations/pyjwt.rst:39 #: ../../migrations/pyjwt.rst:65 ../../migrations/pyjwt.rst:111 #: ../../migrations/pyjwt.rst:130 msgid "PyJWT" msgstr "" #: ../../migrations/pyjwt.rst:36 msgid "" "Similarly, both PyJWT and joserfc use the ``.decode`` method to verify " "and decode a JWT, but the parameter structure differs." msgstr "" #: ../../migrations/pyjwt.rst:57 msgid "Non-plain string key" msgstr "" #: ../../migrations/pyjwt.rst:59 msgid "" "When using a non-plain string key (equivalent to an \"oct\" key) in " "joserfc, such as RSA, EC, or OKP keys, the library provides built-in " "implementations to handle these key types. This eliminates the need for " "manual key handling, which is required in PyJWT." msgstr "" #: ../../migrations/pyjwt.rst:63 msgid "Let's take an example using an RSA key:" msgstr "" #: ../../migrations/pyjwt.rst:93 msgid "Claims validation" msgstr "" #: ../../migrations/pyjwt.rst:95 msgid "" "Both PyJWT and ``joserfc`` provide mechanisms for claims validation, " "although they differ in their approach." msgstr "" #: ../../migrations/pyjwt.rst:98 msgid "" "In PyJWT, claims validation is performed within the ``.decode`` method " "itself. When decoding a token, you can specify options such as " "``verify_exp`` to validate the expiration time, ``verify_aud`` to " "validate the audience, and other options for additional claim " "validations. Claims validation is an integral part of the decoding " "process." msgstr "" #: ../../migrations/pyjwt.rst:103 msgid "" "On the other hand, ``joserfc`` follows a different approach by separating" " the decoding and claims validation steps. The .decode method in joserfc " "is focused solely on decoding the token and extracting the header and " "payload information. Claims validation is performed separately using " "claims validators." msgstr "" #: ../../migrations/pyjwt.rst:109 msgid "Verify \"exp\"" msgstr "" #: ../../migrations/pyjwt.rst:128 msgid "Required claims" msgstr "" #: ../../migrations/pyjwt.rst:149 msgid "" "The ``JWTClaimsRegistry`` accepts each claim as an `Individual Claims " "Requests `_ JSON object. You can learn more from " ":ref:`claims`." msgstr "" #: ../../migrations/python-jose.rst:2 #, fuzzy msgid "Migrating from python-jose" msgstr "从 PyJWT 迁移到 joserfc" #: ../../migrations/python-jose.rst:4 msgid "" "``python-jose`` supports all JOSE specifications, similar to ``joserfc``." " However, there are significant differences in code structure, method " "names, and parameter usage. Additionally, ``joserfc`` offers built-in " "Python type hints, enhancing code readability and type safety." msgstr "" #: ../../migrations/python-jose.rst:9 msgid "" "Another key difference is that ``python-jose`` only supports compact " "serialization and deserialization, whereas ``joserfc`` supports both " "compact and JSON serialization formats, offering greater flexibility in " "handling JOSE data." msgstr "" #: ../../migrations/python-jose.rst:16 msgid "" "In ``python-jose``, the methods used for serialization and " "deserialization are ``jws.sign`` and ``jws.verify``, respectively." msgstr "" #: ../../migrations/python-jose.rst:19 msgid "" "On the other hand, ``joserfc`` uses ``jws.serialize_compact`` for " "serialization and ``jws.deserialize_compact`` for deserialization." msgstr "" #: ../../migrations/python-jose.rst:22 ../../migrations/python-jose.rst:63 #: ../../migrations/python-jose.rst:92 msgid "python-jose" msgstr "" #: ../../migrations/python-jose.rst:46 msgid "" "``joserfc`` is designed to be highly explicit, requiring the use of " "specific key types, payload formats, and other components. For example, " "in the previous example, we explicitly use an OctKey instead of a simple " "string. Additionally, since JWS in joserfc only supports encoding strings" " and bytes, you cannot pass a dictionary directly as the payload. " "Instead, the payload must first be converted to a JSON string using " "json.dumps. This explicit approach ensures better type safety and clarity" " in your code." msgstr "" #: ../../migrations/python-jose.rst:57 msgid "" "In ``python-jose``, the methods used for encryption and decryption are " "``jwe.encrypt`` and ``jwe.decrypt``. However, since ``joserfc`` supports " "both compact and JSON serialization formats, it provides distinct " "methods: ``jwe.encrypt_compact`` and ``jwe.decrypt_compact`` for compact " "serialization, ensuring clear differentiation between the formats and " "greater flexibility in handling JWE operations." msgstr "" #: ../../migrations/python-jose.rst:87 msgid "" "The ``jwt`` module in ``python-jose`` supports only JWS (JSON Web " "Signature) mode, whereas ``joserfc`` provides support for both JWS and " "JWE (JSON Web Encryption) modes. Although both libraries utilize the " "``encode`` and ``decode`` methods, their parameters differ significantly " "in terms of structure and flexibility." msgstr "" #: ../../migrations/python-jose.rst:115 msgid "get_unverified_header" msgstr "" #: ../../migrations/python-jose.rst:117 msgid "" "The ``jwt`` module in python-jose provides a method called " "``get_unverified_header``, which allows extracting the header from a JWT " "without verifying its signature." msgstr "" #: ../../migrations/python-jose.rst:120 msgid "In ``joserfc``, we can get the unverified header with:" msgstr "" #: ../../migrations/python-jose.rst:133 msgid "JWK" msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/recipes.po000066400000000000000000000155171501424510600217250ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-15 13:31+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" #: ../../recipes/azure.rst:2 msgid "Dynamic keys for Azure" msgstr "" #: ../../recipes/azure.rst:4 msgid "" "In scenarios where you need to decode a JWT received from Azure " "(Microsoft), you may encounter a situation where you are unaware of the " "public key required for the decoding process until after the token " "arrives. In such cases, you will typically need to retrieve the key set " "dynamically from the ``iss`` (issuer) value contained within the JWT." msgstr "" #: ../../recipes/azure.rst:10 msgid "" "Let's illustrate this process using a JWT token extracted from " "Microsoft's official documentation `Access tokens in the Microsoft " "identity platform `_:" msgstr "" #: ../../recipes/azure.rst:31 msgid "" "This token, obtained from Microsoft's official documentation, serves as " "an example for decoding JWTs originating from Azure. The decoded payload " "might look like:" msgstr "" #: ../../recipes/azure.rst:57 msgid "Steps for decoding" msgstr "" #: ../../recipes/azure.rst:59 msgid "" "In order to decode JWT tokens from Azure, it is essential to retrieve the" " necessary information from Microsoft's OpenID configuration, including " "the JSON Web Key Set (JWK Set) URI. This information is crucial for " "verifying the tokens." msgstr "" #: ../../recipes/azure.rst:64 msgid "OpenID Configuration Endpoint" msgstr "" #: ../../recipes/azure.rst:66 msgid "" "You can obtain the OpenID configuration endpoint from Microsoft by " "forming a URL in the following format:" msgstr "" #: ../../recipes/azure.rst:73 msgid "" "In the example provided, replace {tenant} with your specific Azure tenant" " ID or the tenant's globally unique identifier (GUID). The resulting URL " "will lead you to the OpenID configuration details. Then, the OpenID " "configuration endpoint for the above example could be:" msgstr "" #: ../../recipes/azure.rst:83 msgid "JWK Set URI" msgstr "" #: ../../recipes/azure.rst:85 msgid "" "Within the OpenID configuration details, you will find the JSON Web Key " "Set (JWK Set) URI. This URI is used to access the keys required for " "verifying JWT tokens. The JWK Set URI can typically be found within the " "configuration as follows:" msgstr "" #: ../../recipes/azure.rst:93 msgid "" "Once again, remember to replace {tenant} with your Azure tenant ID or the" " appropriate identifier. In the above example, the ``jwks_uri`` could be:" msgstr "" #: ../../recipes/azure.rst:101 msgid "Validating JWT Tokens" msgstr "" #: ../../recipes/azure.rst:103 msgid "" "Once you have retrieved the JSON Web Key Set (JWK Set) from the JWK Set " "URI provided in the OpenID configuration, you can proceed to validate JWT" " tokens." msgstr "" #: ../../recipes/azure.rst:107 msgid "Using a Callable Key" msgstr "" #: ../../recipes/azure.rst:109 msgid "" "In ``joserfc``, a callable key is a powerful feature that allows you to " "dynamically retrieve and use the appropriate JSON Web Key (JWK) for token" " decoding. In the context of Azure tokens, you can implement a callable " "key to fetch the JWKs from the JWK Set URI and select the correct key " "based on the kid (Key ID) in the token's header." msgstr "" #: ../../recipes/azure.rst:139 msgid "" "When using the callable key method in ``joserfc`` to decode the tokens, " "it retrieves the key dynamically on each token decoding request. However," " you may encounter performance issues due to the repeated retrieval of " "keys. In such cases, it's advisable to optimize the callable key by " "implementing key set caching based on the issuer." msgstr "" #: ../../recipes/azure.rst:144 msgid "Let's enhance the callable key method to improve its efficiency." msgstr "" #: ../../recipes/azure.rst:164 msgid "" "In this enhanced callable key, an LRU (Least Recently Used) cache is used" " to store JWK Sets for different issuers. When decoding a token, the " "callable key function first checks if the JWK Set for the specific issuer" " is available in the cache. If it's not, it fetches the JWK Set for the " "issuer, caches it, and then selects the appropriate JWK based on the kid." " This caching mechanism significantly reduces the network requests for " "JWK Sets and improves the efficiency of token decoding." msgstr "" #: ../../recipes/azure.rst:172 msgid "Manual Token Decoding" msgstr "" #: ../../recipes/azure.rst:174 msgid "" "If you prefer a more hands-on approach and want to decode the token step " "by step, you can opt for a manual decoding process. This method allows " "you to extract the token string and work with it directly. Since the " "token is a JWT in JWS format, you can utilize the ``extract_compact`` " "method from the JWS module to obtain the necessary information. The " "result of this extraction is an object of type " ":class:`~joserfc.jws.CompactSignature`." msgstr "" #: ../../recipes/azure.rst:187 msgid "" "Similar to the approach detailed in the \"Using a Callable Key\" section," " you can retrieve the key set based on the issuer (``iss``) claim. This " "method allows you to access the necessary keys for token verification." msgstr "" #: ../../recipes/azure.rst:204 msgid "" "Once you have obtained the key set based on the issuer (``iss``) claim, " "you can use this set of keys to decode the token." msgstr "" #: ../../recipes/openssl.rst:2 msgid "Using OpenSSL command" msgstr "" #: ../../recipes/openssl.rst:7 msgid "" "JOSE RFC provides a method :meth:`JWKRegistry.generate_key` for " "generating keys to be used for JWS/JWE/JWT. However, you can also use " "other tools to generate the keys, here lists some of the commands you " "might find helpful for ``openssl``." msgstr "" #: ../../recipes/openssl.rst:13 msgid "Generating EC keys" msgstr "" #: ../../recipes/openssl.rst:16 msgid "EC key with crv P-256" msgstr "" #: ../../recipes/openssl.rst:26 msgid "Using OpenSSL command line tool:" msgstr "" #: ../../recipes/openssl.rst:36 msgid "OpenSSL encourage using prime256v1 instead of secp256r1" msgstr "" #: ../../recipes/openssl.rst:40 msgid "EC key with crv P-384" msgstr "" #: ../../recipes/openssl.rst:60 msgid "EC key with crv P-512" msgstr "" #: ../../recipes/openssl.rst:78 msgid "" "It is **secp521r1**, not secp512r1. But the \"crv\" value in EC Key is " "\"P-512\"." msgstr "" #: ../../recipes/openssl.rst:82 msgid "EC key with crv secp256k1" msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/security.po000066400000000000000000000042331501424510600221330ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-02-28 11:54+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.17.0\n" #: ../../security.rst:2 msgid "Security" msgstr "安全" #: ../../security.rst:4 msgid "" "If you think you have found a potential security vulnerability in " "``joserfc``, please email directly." msgstr "" #: ../../security.rst:7 msgid "Do not file a public issue." msgstr "" #: ../../security.rst:9 msgid "" "Please do not disclose this to anyone else. We will retrieve a CVE " "identifier if necessary and give you full credit under whatever name or " "alias you provide. We will only request an identifier when we have a fix " "and can publish it in a release." msgstr "" #: ../../security.rst:15 msgid "The Process" msgstr "" #: ../../security.rst:17 msgid "Here is the process when we have received a security report:" msgstr "" #: ../../security.rst:19 msgid "we will reply to you in 24 hours" msgstr "" #: ../../security.rst:20 msgid "" "we will confirm it in 2 days, if we can't reproduce it, we will send " "emails to you for more information" msgstr "" #: ../../security.rst:22 msgid "" "we will fix the issue in 1 week after we confirm it. If we can't fix it " "for the moment, we will let you know." msgstr "" #: ../../security.rst:24 msgid "" "we will push the source code to GitHub when it has been released in PyPI " "for 1 week." msgstr "" #: ../../security.rst:26 msgid "if necessary, we will retrieve a CVE after releasing to PyPI." msgstr "" #: ../../security.rst:29 msgid "Previous CVEs" msgstr "" #: ../../security.rst:31 msgid "CVE-2024-37568: fixed in 0.11.0" msgstr "" #~ msgid "Waiting..." #~ msgstr "" joserfc-1.1.0/docs/locales/zh/LC_MESSAGES/stability.po000066400000000000000000000053131501424510600222700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2023. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-07-15 14:44+0900\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" "Language-Team: zh \n" "Plural-Forms: nplurals=1; plural=0\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.1\n" #: ../../stability.rst:2 msgid "API stability" msgstr "稳定性" #: ../../stability.rst:4 msgid "" "The API of joserfc is currently a work in progress and may not be " "considered fully stable. However, with each release, the API stability is" " improving and getting closer to a stable state." msgstr "" #: ../../stability.rst:9 msgid "Interfaces" msgstr "" #: ../../stability.rst:11 msgid "" "Starting from **version 0.5.0**, the method names and their parameters " "are expected to remain stable. This means that once you have updated your" " code to use the methods provided by joserfc, you can rely on them " "without the need for frequent changes." msgstr "" #: ../../stability.rst:16 msgid "Python Versions" msgstr "" #: ../../stability.rst:18 msgid "" "``joserfc`` is designed to support Python 3.8 and above. It is " "recommended to use ``joserfc`` with Python versions 3.8 and higher to " "ensure compatibility and take advantage of the latest language features " "and improvements." msgstr "" #: ../../stability.rst:23 msgid "New RFCs" msgstr "" #: ../../stability.rst:25 msgid "" "To maintain a stable and reliable library, joserfc will not introduce new" " RFC implementations until the 1.0.0 release. This approach ensures that " "the existing functionality is thoroughly tested and the library reaches a" " mature state before incorporating new specifications." msgstr "" #: ../../stability.rst:30 msgid "" "When new RFC implementations are added after the 1.0.0 release, the minor" " version of joserfc will be incremented. This versioning approach helps " "to communicate the introduction of new features and RFC support to users," " while also indicating potential changes to the API and behavior." msgstr "" #: ../../stability.rst:36 msgid "Upgrade notes" msgstr "" #: ../../stability.rst:38 msgid "" "Please note that while efforts are made to maintain compatibility and " "stability, it is always a good practice to thoroughly test and validate " "your code when upgrading to a new version of joserfc to ensure a smooth " "transition and avoid any potential compatibility issues." msgstr "" joserfc-1.1.0/docs/migrations/000077500000000000000000000000001501424510600162465ustar00rootroot00000000000000joserfc-1.1.0/docs/migrations/authlib.rst000066400000000000000000000076241501424510600204410ustar00rootroot00000000000000Migrating from Authlib ====================== ``joserfc`` is derived from Authlib and shares similar implementations of algorithms. However, it is important to note that the APIs are different between the two libraries. When migrating your code from Authlib to ``joserfc``, you will need to update your code to accommodate the new API structure and functionality. JWT --- Migrating JWT (JSON Web Token) operations from Authlib to ``joserfc`` involves some considerations regarding security design and the allowed algorithms. jwt.encode ~~~~~~~~~~ The interface for JWT operations in both ``authlib.jose`` and ``joserfc`` is quite similar. In both libraries, you can encode a JWT using the ``jwt.encode(header, payload, key)`` method. .. code-block:: python :caption: Authlib from authlib.jose import jwt jwt.encode({"alg": "HS256"}, {"iss": "https://jose.authlib.org"}, "secret") .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("secret") jwt.encode({"alg": "HS256"}, {"iss": "https://jose.authlib.org"}, key) jwt.decode ~~~~~~~~~~ The ``jwt.decode`` method in Authlib and ``joserfc`` behaves differently when it comes to claims validation. In Authlib, the ``jwt.decode`` method combines the decoding of the JWT and the validation of its claims into a single step. .. code-block:: python from authlib.jose import jwt s = '...' # The JWT to decode # Decode and validate the token's claims token = jwt.decode(s, key, claims_options) In ``joserfc``, the ``jwt.decode`` process is split into two steps: decoding the token and then separately validating its claims. This approach provides more flexibility and allows for granular control over the validation process. .. code-block:: python from joserfc import jwt s = '...' # The JWT to decode token = jwt.decode(s, key) claims_requests = jwt.JWTClaimsRegistry( iss={"essential": True, "value": "https://authlib.org"}, ) claims_requests.validate(token.claims) You can learn more about :ref:`claims validation ` on the :ref:`jwt` guide. JWS --- When migrating JWS (JSON Web Signature) operations from Authlib to ``joserfc``, follow these steps: .. code-block:: python :caption: Authlib :emphasize-lines: 1,2 from authlib.jose import JsonWebSignature jws = JsonWebSignature() protected = {'alg': 'HS256'} payload = b"example" value = jws.serialize_compact(protected, payload, "secret") jws.deserialize_compact(value, "secret") .. code-block:: python :caption: joserfc from joserfc import jws from joserfc.jwk import OctKey key = OctKey.import_key("secret") protected = {"alg': 'HS256"} payload = b"example" value = jws.serialize_compact(protected, payload, key) jws.deserialize_compact(value, key) Above is a simple example of using the ``HS256`` algorithm for JWS. If you would like to explore further and learn more about JWS, we recommend referring to the comprehensive :ref:`jws` guide. JWE --- The method names for JWE serialization and deserialization are different between Authlib and ``joserfc``. In Authlib, the methods for JWE serialization and deserialization are: - ``.serialize_compact(header, payload, key)`` - ``.deserialize_compact(token, key)`` .. code-block:: python from authlib.jose import JsonWebEncryption jwe = JsonWebEncryption() jwe.serialize_compact(header, payload, key) jwe.deserialize_compact(token, key) In ``joserfc``, the equivalent methods for JWE serialization and deserialization are: - ``.encrypt_compact(header, payload, key)`` - ``.decrypt_compact(token, key)`` .. code-block:: python from joserfc import jwe jwe.encrypt_compact(header, payload, key) jwe.decrypt_compact(token, key) If you would like to explore further and learn more about JWS, we recommend referring to the comprehensive :ref:`jwe` guide. joserfc-1.1.0/docs/migrations/index.rst000066400000000000000000000002441501424510600201070ustar00rootroot00000000000000Migrations ========== Here are some migration guides to help you transition from other libraries to ``joserfc``: .. toctree:: authlib pyjwt python-jose joserfc-1.1.0/docs/migrations/pyjwt.rst000066400000000000000000000112111501424510600201510ustar00rootroot00000000000000Migrating from PyJWT ==================== When migrating from PyJWT to ``joserfc``, there are a few key differences to be aware of. ``joserfc`` provides implementations for JWS (JSON Web Signature), JWE (JSON Web Encryption), JWK (JSON Web Key), and JWT (JSON Web Token), whereas PyJWT focuses primarily on JWS and JWT. Additionally, joserfc supports both JWT on JWS and JWT on JWE, offering more flexibility for token handling. jwt.encode ---------- Both PyJWT and joserfc use the ``.encode`` method to generate a JWT, but the parameter structure differs between the two libraries. .. code-block:: python :caption: PyJWT import jwt # jwt.encode(payload, key, algorithm) encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("secret") # use an explicit key # jwt.encode(header, payload, key) encoded_jwt = jwt.encode({"alg": "HS256"}, {"some": "payload"}, key) jwt.decode ---------- Similarly, both PyJWT and joserfc use the ``.decode`` method to verify and decode a JWT, but the parameter structure differs. .. code-block:: python :caption: PyJWT token = jwt.decode(encoded_jwt, "secret", algorithms=["HS256"]) # => {"some": "payload"} .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("secret") token = jwt.decode(encoded_jwt, key) # => token.header : {"alg": "HS256"} # => token.claims : {"some": "payload"} Non-plain string key -------------------- When using a non-plain string key (equivalent to an "oct" key) in joserfc, such as RSA, EC, or OKP keys, the library provides built-in implementations to handle these key types. This eliminates the need for manual key handling, which is required in PyJWT. Let's take an example using an RSA key: .. code-block:: python :caption: PyJWT import jwt from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend private_pem = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..." private_key = serialization.load_pem_private_key( private_pem, password=None, backend=default_backend() ) encoded_jwt = jwt.encode({"some": "payload"}, private_key, algorithm="RS256") .. code-block:: python :caption: joserfc from joserfc.jwk import RSAKey from joserfc import jwt private_pem = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..." # Import the RSA key using joserfc's RSAKey key = RSAKey.import_key(private_pem) header = {'alg': 'RS256'} payload = {'some': 'payload'} encoded = jwt.encode(header, payload, key) Claims validation ----------------- Both PyJWT and ``joserfc`` provide mechanisms for claims validation, although they differ in their approach. In PyJWT, claims validation is performed within the ``.decode`` method itself. When decoding a token, you can specify options such as ``verify_exp`` to validate the expiration time, ``verify_aud`` to validate the audience, and other options for additional claim validations. Claims validation is an integral part of the decoding process. On the other hand, ``joserfc`` follows a different approach by separating the decoding and claims validation steps. The .decode method in joserfc is focused solely on decoding the token and extracting the header and payload information. Claims validation is performed separately using claims validators. Verify "exp" ~~~~~~~~~~~~ .. code-block:: python :caption: PyJWT import jwt jwt.decode(encoded_jwt, options={"verify_exp": True}) .. code-block:: python :caption: joserfc from joserfc import jwt # claims requests has built-in validators for exp, nbf, iat claims_requests = jwt.JWTClaimsRegistry() token = jwt.decode(encoded_jwt, key) claims_requests.validate(token.claims) Required claims ~~~~~~~~~~~~~~~ .. code-block:: python :caption: PyJWT import jwt jwt.decode(encoded_jwt, options={"require": ["exp", "iss", "sub"]}) .. code-block:: python :caption: joserfc from joserfc import jwt claims_requests = jwt.JWTClaimsRegistry( exp={"essential": True}, iss={"essential": True}, sub={"essential": True}, ) token = jwt.decode(encoded_jwt, key) claims_requests.validate(token.claims) The ``JWTClaimsRegistry`` accepts each claim as an `Individual Claims Requests `_ JSON object. You can learn more from :ref:`claims`. .. _ClaimsOption: http://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests joserfc-1.1.0/docs/migrations/python-jose.rst000066400000000000000000000105541501424510600212640ustar00rootroot00000000000000Migrating from python-jose ========================== ``python-jose`` supports all JOSE specifications, similar to ``joserfc``. However, there are significant differences in code structure, method names, and parameter usage. Additionally, ``joserfc`` offers built-in Python type hints, enhancing code readability and type safety. Another key difference is that ``python-jose`` only supports compact serialization and deserialization, whereas ``joserfc`` supports both compact and JSON serialization formats, offering greater flexibility in handling JOSE data. JWS --- In ``python-jose``, the methods used for serialization and deserialization are ``jws.sign`` and ``jws.verify``, respectively. On the other hand, ``joserfc`` uses ``jws.serialize_compact`` for serialization and ``jws.deserialize_compact`` for deserialization. .. code-block:: python :caption: python-jose from jose import jws signed = jws.sign({'a': 'b'}, 'secret', algorithm='HS256') jws.verify(signed, 'secret', algorithms='HS256') # the verify only returns the payload .. code-block:: python :caption: joserfc import json from joserfc import jws from joserfc.jwk import OctKey key = OctKey.import_key("secret") protected = {"alg": "HS256"} signed = jws.serialize_compact(protected, json.dumps({'a': 'b'}), key) obj = jws.deserialize_compact(text, key) # access the payload with obj.payload .. important:: ``joserfc`` is designed to be highly explicit, requiring the use of specific key types, payload formats, and other components. For example, in the previous example, we explicitly use an OctKey instead of a simple string. Additionally, since JWS in joserfc only supports encoding strings and bytes, you cannot pass a dictionary directly as the payload. Instead, the payload must first be converted to a JSON string using json.dumps. This explicit approach ensures better type safety and clarity in your code. JWE --- In ``python-jose``, the methods used for encryption and decryption are ``jwe.encrypt`` and ``jwe.decrypt``. However, since ``joserfc`` supports both compact and JSON serialization formats, it provides distinct methods: ``jwe.encrypt_compact`` and ``jwe.decrypt_compact`` for compact serialization, ensuring clear differentiation between the formats and greater flexibility in handling JWE operations. .. code-block:: python :caption: python-jose from jose import jwe encrypted = jwe.encrypt('Hello, World!', 'asecret128bitkey', algorithm='dir', encryption='A128GCM') jwe.decrypt(encrypted, 'asecret128bitkey') # => 'Hello, World!' .. code-block:: python :caption: joserfc from joserfc import jwe from joserfc.jwk import OctKey key = OctKey.generate_key(128) # 128bit key protected = {'alg': 'dir', 'enc': 'A128GCM'} encrypted = jwe.encrypt_compact(protected, 'Hello, World!', key) obj = jwe.decrypt_compact(encrypted, key) # obj.payload => b'Hello, World!' JWT --- The ``jwt`` module in ``python-jose`` supports only JWS (JSON Web Signature) mode, whereas ``joserfc`` provides support for both JWS and JWE (JSON Web Encryption) modes. Although both libraries utilize the ``encode`` and ``decode`` methods, their parameters differ significantly in terms of structure and flexibility. .. code-block:: python :caption: python-jose from jose import jwt encoded = jwt.encode({'a': 'b'}, 'secret', algorithm='HS256') jwt.decode(encoded, 'secret', algorithms='HS256') # => {'a': 'b'} .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("secret") # jwt.encode(header, payload, key) encoded = jwt.encode({"alg": "HS256"}, {'a': 'b'}, key) token = jwt.decode(encoded, key) # => token.header : {"alg": "HS256"} # => token.claims : {"some": "payload"} get_unverified_header ~~~~~~~~~~~~~~~~~~~~~ The ``jwt`` module in python-jose provides a method called ``get_unverified_header``, which allows extracting the header from a JWT without verifying its signature. In ``joserfc``, we can get the unverified header with: .. code-block:: python :caption: joserfc from typing import Any from joserfc import jws def get_unverified_header(token: str) -> dict[str, Any]: token_content = jws.extract_compact(token.encode()) return token_content.protected JWK --- joserfc-1.1.0/docs/recipes/000077500000000000000000000000001501424510600155245ustar00rootroot00000000000000joserfc-1.1.0/docs/recipes/azure.rst000066400000000000000000000206311501424510600174060ustar00rootroot00000000000000Dynamic keys for Azure ====================== In scenarios where you need to decode a JWT received from Azure (Microsoft), you may encounter a situation where you are unaware of the public key required for the decoding process until after the token arrives. In such cases, you will typically need to retrieve the key set dynamically from the ``iss`` (issuer) value contained within the JWT. Let's illustrate this process using a JWT token extracted from Microsoft's official documentation `Access tokens in the Microsoft identity platform `_: .. _ms_doc_url: https://learn.microsoft.com/en-us/azure/active-directory/develop/access-tokens .. code-block:: none eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Imk2bEdrM0ZaenhSY1ViMkMzbkVRN3N5SEpsWSJ9 .eyJhdWQiOiI2ZTc0MTcyYi1iZTU2LTQ4NDMtOWZmNC1lNjZhMzliYjEyZTMiLCJpc3MiOiJodHRwczovL2x vZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3Y yLjAiLCJpYXQiOjE1MzcyMzEwNDgsIm5iZiI6MTUzNzIzMTA0OCwiZXhwIjoxNTM3MjM0OTQ4LCJhaW8iOiJ BWFFBaS84SUFBQUF0QWFaTG8zQ2hNaWY2S09udHRSQjdlQnE0L0RjY1F6amNKR3hQWXkvQzNqRGFOR3hYZDZ 3TklJVkdSZ2hOUm53SjFsT2NBbk5aY2p2a295ckZ4Q3R0djMzMTQwUmlvT0ZKNGJDQ0dWdW9DYWcxdU9UVDI yMjIyZ0h3TFBZUS91Zjc5UVgrMEtJaWpkcm1wNjlSY3R6bVE9PSIsImF6cCI6IjZlNzQxNzJiLWJlNTYtNDg 0My05ZmY0LWU2NmEzOWJiMTJlMyIsImF6cGFjciI6IjAiLCJuYW1lIjoiQWJlIExpbmNvbG4iLCJvaWQiOiI 2OTAyMjJiZS1mZjFhLTRkNTYtYWJkMS03ZTRmN2QzOGU0NzQiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhYmV saUBtaWNyb3NvZnQuY29tIiwicmgiOiJJIiwic2NwIjoiYWNjZXNzX2FzX3VzZXIiLCJzdWIiOiJIS1pwZmF IeVdhZGVPb3VZbGl0anJJLUtmZlRtMjIyWDVyclYzeERxZktRIiwidGlkIjoiNzJmOTg4YmYtODZmMS00MWF mLTkxYWItMmQ3Y2QwMTFkYjQ3IiwidXRpIjoiZnFpQnFYTFBqMGVRYTgyUy1JWUZBQSIsInZlciI6IjIuMCJ9 .pj4N-w_3Us9DrBLfpCt This token, obtained from Microsoft's official documentation, serves as an example for decoding JWTs originating from Azure. The decoded payload might look like: .. code-block:: json { "aud": "6e74172b-be56-4843-9ff4-e66a39bb12e3", "iss": "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0", "iat": 1537231048, "nbf": 1537231048, "exp": 1537234948, "aio": "AXQAi/8IAAAAtAaZLo3ChMif6KOnttRB7eBq4/DccQzjcJGxPYy/C3jDa...", "azp": "6e74172b-be56-4843-9ff4-e66a39bb12e3", "azpacr": "0", "name": "Abe Lincoln", "oid": "690222be-ff1a-4d56-abd1-7e4f7d38e474", "preferred_username": "abeli@microsoft.com", "rh": "I", "scp": "access_as_user", "sub": "HKZpfaHyWadeOouYlitjrI-KffTm222X5rrV3xDqfKQ", "tid": "72f988bf-86f1-41af-91ab-2d7cd011db47", "uti": "fqiBqXLPj0eQa82S-IYFAA", "ver": "2.0" } Steps for decoding ------------------ In order to decode JWT tokens from Azure, it is essential to retrieve the necessary information from Microsoft's OpenID configuration, including the JSON Web Key Set (JWK Set) URI. This information is crucial for verifying the tokens. OpenID Configuration Endpoint ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can obtain the OpenID configuration endpoint from Microsoft by forming a URL in the following format: .. code-block:: none https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration In the example provided, replace {tenant} with your specific Azure tenant ID or the tenant's globally unique identifier (GUID). The resulting URL will lead you to the OpenID configuration details. Then, the OpenID configuration endpoint for the above example could be: .. code-block:: none https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/v2.0/.well-known/openid-configuration JWK Set URI ~~~~~~~~~~~ Within the OpenID configuration details, you will find the JSON Web Key Set (JWK Set) URI. This URI is used to access the keys required for verifying JWT tokens. The JWK Set URI can typically be found within the configuration as follows: .. code-block:: none https://login.microsoftonline.com/{tenant}/discovery/v2.0/keys Once again, remember to replace {tenant} with your Azure tenant ID or the appropriate identifier. In the above example, the ``jwks_uri`` could be: .. code-block:: none https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/discovery/v2.0/keys Validating JWT Tokens ~~~~~~~~~~~~~~~~~~~~~ Once you have retrieved the JSON Web Key Set (JWK Set) from the JWK Set URI provided in the OpenID configuration, you can proceed to validate JWT tokens. Using a Callable Key -------------------- In ``joserfc``, a callable key is a powerful feature that allows you to dynamically retrieve and use the appropriate JSON Web Key (JWK) for token decoding. In the context of Azure tokens, you can implement a callable key to fetch the JWKs from the JWK Set URI and select the correct key based on the kid (Key ID) in the token's header. .. code-block:: python import json import requests from joserfc.jws import CompactSignature from joserfc.jwk import KeySet from joserfc import jwt def load_key(obj: CompactSignature): claims = json.loads(obj.payload) issuer_url = claims['iss'] # retrieve OpenID Configuration Endpoint openid_configuration_endpoint = f'{issuer_url}/.well-known/openid-configuration' resp = requests.get(openid_configuration_endpoint) # retrieve JWK Set URI jwks_uri = resp.json()['jwks_uri'] resp = requests.get(jwks_uri) key_set = KeySet.import_key_set(resp.json()) return key_set # pass load_key as a callable key to `jwt.decode` method jwt.decode(token_string, load_key) When using the callable key method in ``joserfc`` to decode the tokens, it retrieves the key dynamically on each token decoding request. However, you may encounter performance issues due to the repeated retrieval of keys. In such cases, it's advisable to optimize the callable key by implementing key set caching based on the issuer. Let's enhance the callable key method to improve its efficiency. .. code-block:: python import functools @functools.cache def fetch_key_set(issuer: str): openid_configuration_endpoint = f'{issuer}/.well-known/openid-configuration' resp = requests.get(openid_configuration_endpoint) jwks_uri = resp.json()['jwks_uri'] resp = requests.get(jwks_uri) return KeySet.import_key_set(resp.json()) def load_key(obj: CompactSignature): claims = json.loads(obj.payload) key_set = fetch_key_set(claims['iss']) key = key_set.get_by_kid(obj.headers()['kid']) return key In this enhanced callable key, an LRU (Least Recently Used) cache is used to store JWK Sets for different issuers. When decoding a token, the callable key function first checks if the JWK Set for the specific issuer is available in the cache. If it's not, it fetches the JWK Set for the issuer, caches it, and then selects the appropriate JWK based on the kid. This caching mechanism significantly reduces the network requests for JWK Sets and improves the efficiency of token decoding. Manual Token Decoding --------------------- If you prefer a more hands-on approach and want to decode the token step by step, you can opt for a manual decoding process. This method allows you to extract the token string and work with it directly. Since the token is a JWT in JWS format, you can utilize the ``extract_compact`` method from the JWS module to obtain the necessary information. The result of this extraction is an object of type :class:`~joserfc.jws.CompactSignature`. .. code-block:: python from joserfc.jws import extract_compact, CompactSignature obj: CompactSignature = extract_compact(token_string) Similar to the approach detailed in the "Using a Callable Key" section, you can retrieve the key set based on the issuer (``iss``) claim. This method allows you to access the necessary keys for token verification. .. code-block:: python @functools.cache def fetch_key_set(issuer: str): openid_configuration_endpoint = f'{issuer}/.well-known/openid-configuration' resp = requests.get(openid_configuration_endpoint) jwks_uri = resp.json()['jwks_uri'] resp = requests.get(jwks_uri) return KeySet.import_key_set(resp.json()) claims = json.loads(obj.payload) key_set = fetch_key_set(claims['iss']) Once you have obtained the key set based on the issuer (``iss``) claim, you can use this set of keys to decode the token. .. code-block:: python from joserfc import jwt token = jwt.decode(token_string, key_set) joserfc-1.1.0/docs/recipes/openssl.rst000066400000000000000000000047541501424510600177530ustar00rootroot00000000000000Using OpenSSL command ===================== .. module:: joserfc.jwk :noindex: JOSE RFC provides a method :meth:`JWKRegistry.generate_key` for generating keys to be used for JWS/JWE/JWT. However, you can also use other tools to generate the keys, here lists some of the commands you might find helpful for ``openssl``. Generating EC keys ------------------ EC key with crv P-256 ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from joserfc.jwk import JWKRegistry key = JWKRegistry.generate_key('EC', 'P-256', private=True) private_pem = key.as_bytes(private=True) public_pem = key.as_bytes(private=False) Using OpenSSL command line tool: .. code-block:: shell # generate private key openssl ecparam -name prime256v1 -genkey -noout -out ec-p256-private.pem # extract public key openssl ec -in ec-p256-private.pem -pubout -out ec-p256-public.pem .. hint:: OpenSSL encourage using prime256v1 instead of secp256r1 EC key with crv P-384 ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from joserfc.jwk import JWKRegistry key = JWKRegistry.generate_key('EC', 'P-384', private=True) private_pem = key.as_bytes(private=True) public_pem = key.as_bytes(private=False) .. code-block:: shell # generate private key openssl ecparam -name secp384r1 -genkey -noout -out ec-p384-private.pem # extract public key openssl ec -in ec-p384-private.pem -pubout -out ec-p384-public.pem EC key with crv P-512 ~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from joserfc.jwk import JWKRegistry key = JWKRegistry.generate_key('EC', 'P-512', private=True) private_pem = key.as_bytes(private=True) public_pem = key.as_bytes(private=False) .. code-block:: shell # generate private key openssl ecparam -name secp521r1 -genkey -noout -out ec-p512-private.pem # extract public key openssl ec -in ec-p512-private.pem -pubout -out ec-p512-public.pem .. note:: It is **secp521r1**, not secp512r1. But the "crv" value in EC Key is "P-512". EC key with crv secp256k1 ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python from joserfc.jwk import JWKRegistry key = JWKRegistry.generate_key('EC', 'secp256k1', private=True) private_pem = key.as_bytes(private=True) public_pem = key.as_bytes(private=False) .. code-block:: shell # generate private key openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-private.pem # extract public key openssl ec -in ec-secp256k1-private.pem -pubout -out ec-secp256k1-public.pem joserfc-1.1.0/docs/security.rst000066400000000000000000000017441501424510600165010ustar00rootroot00000000000000Security ======== If you think you have found a potential security vulnerability in ``joserfc``, please email directly. .. warning:: Do not file a public issue. Please do not disclose this to anyone else. We will retrieve a CVE identifier if necessary and give you full credit under whatever name or alias you provide. We will only request an identifier when we have a fix and can publish it in a release. The Process ----------- Here is the process when we have received a security report: 1. we will reply to you in 24 hours 2. we will confirm it in 2 days, if we can't reproduce it, we will send emails to you for more information 3. we will fix the issue in 1 week after we confirm it. If we can't fix it for the moment, we will let you know. 4. we will push the source code to GitHub when it has been released in PyPI for 1 week. 5. if necessary, we will retrieve a CVE after releasing to PyPI. Previous CVEs ------------- - CVE-2024-37568: fixed in 0.11.0 joserfc-1.1.0/docs/stability.rst000066400000000000000000000031351501424510600166320ustar00rootroot00000000000000API stability ============= The API of joserfc is currently a work in progress and may not be considered fully stable. However, with each release, the API stability is improving and getting closer to a stable state. Interfaces ---------- Starting from **version 0.5.0**, the method names and their parameters are expected to remain stable. This means that once you have updated your code to use the methods provided by joserfc, you can rely on them without the need for frequent changes. Python Versions --------------- ``joserfc`` is designed to support Python 3.8 and above. It is recommended to use ``joserfc`` with Python versions 3.8 and higher to ensure compatibility and take advantage of the latest language features and improvements. New RFCs --------- To maintain a stable and reliable library, joserfc will not introduce new RFC implementations until the 1.0.0 release. This approach ensures that the existing functionality is thoroughly tested and the library reaches a mature state before incorporating new specifications. When new RFC implementations are added after the 1.0.0 release, the minor version of joserfc will be incremented. This versioning approach helps to communicate the introduction of new features and RFC support to users, while also indicating potential changes to the API and behavior. Upgrade notes ------------- Please note that while efforts are made to maintain compatibility and stability, it is always a good practice to thoroughly test and validate your code when upgrading to a new version of joserfc to ensure a smooth transition and avoid any potential compatibility issues. joserfc-1.1.0/public/000077500000000000000000000000001501424510600144205ustar00rootroot00000000000000joserfc-1.1.0/public/.nojekyll000066400000000000000000000000001501424510600162360ustar00rootroot00000000000000joserfc-1.1.0/public/CNAME000066400000000000000000000000211501424510600151570ustar00rootroot00000000000000jose.authlib.org joserfc-1.1.0/public/index.html000066400000000000000000000003471501424510600164210ustar00rootroot00000000000000 Redirecting to https://jose.authlib.org/en/ joserfc-1.1.0/public/robots.txt000066400000000000000000000000751501424510600164730ustar00rootroot00000000000000User-agent: * Sitemap: https://jose.authlib.org/sitemap.xml joserfc-1.1.0/pyproject.toml000066400000000000000000000057731501424510600160720ustar00rootroot00000000000000[project] name = "joserfc" description = "The ultimate Python library for JOSE RFCs, including JWS, JWE, JWK, JWA, JWT" authors = [{name = "Hsiaoming Yang", email="me@lepture.com"}] dependencies = [ "cryptography", ] license = {text = "BSD-3-Clause"} requires-python = ">=3.8" dynamic = ["version"] readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Security", "Topic :: Security :: Cryptography", ] [project.optional-dependencies] drafts = ["pycryptodome"] [project.urls] Documentation = "https://jose.authlib.org/" Source = "https://github.com/authlib/joserfc" Blog = "https://blog.authlib.org/" [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] version = {attr = "joserfc.__version__"} [tool.setuptools.packages.find] where = ["src"] [tool.setuptools.package-data] joserfc = ["py.typed"] [dependency-groups] dev = [ "mypy>=1.14.1", "pytest>=8.3.4", "pytest-cov>=5.0.0", "pycryptodome>=3.21.0", "ruff>=0.9.2", "pre-commit>=3.5.0", ] docs = [ "shibuya>=2025.2.20", "sphinx>=7.1.2", "sphinx-contributors>=0.2.7", "sphinx-copybutton>=0.5.2", "sphinx-design>=0.5.0", "sphinx-intl>=2.2.0", "sphinx-sitemap>=2.6.0", ] [tool.ruff] line-length = 120 [tool.pytest.ini_options] pythonpath = ["src", "."] testpaths = ["tests"] filterwarnings = ["error"] [tool.coverage.run] branch = true source = ["joserfc"] [tool.coverage.paths] source = ["src"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "raise NotImplementedError", "@(abc\\.)?abstractmethod", "@overload", "@t.overload", ] [tool.mypy] strict = true python_version = "3.8" files = ["src/joserfc"] show_error_codes = true pretty = true [tool.tox] requires = ["tox>=4.19"] env_list = [ "style", "py310", "py311", "py312", "py313", "docs", "coverage", ] [tool.tox.env_run_base] dependency_groups = ["dev"] commands = [ ["pytest", "--showlocals", "--full-trace", "{posargs}"], ] [tool.tox.env.style] skip_install = true commands = [ ["pre-commit", "run", "--all-files", "--show-diff-on-failure"], ] [tool.tox.env.docs] dependency_groups = ["docs"] commands = [ ["sphinx-build", "--builder", "html", "--fail-on-warning", "docs", "build/sphinx/html"], ] [tool.tox.env.coverage] commands = [ ["pytest", "--cov", "--cov-report", "term:skip-covered", "--cov-report", "html", "{posargs}"], ] joserfc-1.1.0/requirements-dev.lock000066400000000000000000000025141501424510600173150ustar00rootroot00000000000000# This file was autogenerated by uv via the following command: # uv export --no-hashes -o requirements-dev.lock -e . cffi==1.17.1 ; platform_python_implementation != 'PyPy' cfgv==3.4.0 colorama==0.4.6 ; sys_platform == 'win32' coverage==7.6.1 ; python_full_version < '3.9' coverage==7.8.2 ; python_full_version >= '3.9' cryptography==45.0.2 distlib==0.3.9 exceptiongroup==1.3.0 ; python_full_version < '3.11' filelock==3.16.1 ; python_full_version < '3.9' filelock==3.18.0 ; python_full_version >= '3.9' identify==2.6.1 ; python_full_version < '3.9' identify==2.6.12 ; python_full_version >= '3.9' iniconfig==2.1.0 mypy==1.14.1 ; python_full_version < '3.9' mypy==1.15.0 ; python_full_version >= '3.9' mypy-extensions==1.1.0 nodeenv==1.9.1 packaging==25.0 platformdirs==4.3.6 ; python_full_version < '3.9' platformdirs==4.3.8 ; python_full_version >= '3.9' pluggy==1.5.0 ; python_full_version < '3.9' pluggy==1.6.0 ; python_full_version >= '3.9' pre-commit==3.5.0 ; python_full_version < '3.9' pre-commit==4.2.0 ; python_full_version >= '3.9' pycparser==2.22 ; platform_python_implementation != 'PyPy' pycryptodome==3.23.0 pytest==8.3.5 pytest-cov==5.0.0 ; python_full_version < '3.9' pytest-cov==6.1.1 ; python_full_version >= '3.9' pyyaml==6.0.2 ruff==0.11.11 tomli==2.2.1 ; python_full_version <= '3.11' typing-extensions==4.13.2 virtualenv==20.31.2 joserfc-1.1.0/requirements-docs.lock000066400000000000000000000067661501424510600175040ustar00rootroot00000000000000# This file was autogenerated by uv via the following command: # uv export --no-hashes --group docs -o requirements-docs.lock -e . alabaster==0.7.13 ; python_full_version < '3.9' alabaster==0.7.16 ; python_full_version == '3.9.*' alabaster==1.0.0 ; python_full_version >= '3.10' babel==2.17.0 certifi==2025.4.26 cffi==1.17.1 ; platform_python_implementation != 'PyPy' cfgv==3.4.0 charset-normalizer==3.4.2 click==8.1.8 ; python_full_version < '3.10' click==8.2.1 ; python_full_version >= '3.10' colorama==0.4.6 ; sys_platform == 'win32' coverage==7.6.1 ; python_full_version < '3.9' coverage==7.8.2 ; python_full_version >= '3.9' cryptography==45.0.2 distlib==0.3.9 docutils==0.20.1 ; python_full_version < '3.9' docutils==0.21.2 ; python_full_version >= '3.9' exceptiongroup==1.3.0 ; python_full_version < '3.11' filelock==3.16.1 ; python_full_version < '3.9' filelock==3.18.0 ; python_full_version >= '3.9' identify==2.6.1 ; python_full_version < '3.9' identify==2.6.12 ; python_full_version >= '3.9' idna==3.10 imagesize==1.4.1 importlib-metadata==8.5.0 ; python_full_version < '3.9' importlib-metadata==8.7.0 ; python_full_version == '3.9.*' iniconfig==2.1.0 jinja2==3.1.6 markupsafe==2.1.5 ; python_full_version < '3.9' markupsafe==3.0.2 ; python_full_version >= '3.9' mypy==1.14.1 ; python_full_version < '3.9' mypy==1.15.0 ; python_full_version >= '3.9' mypy-extensions==1.1.0 nodeenv==1.9.1 packaging==25.0 platformdirs==4.3.6 ; python_full_version < '3.9' platformdirs==4.3.8 ; python_full_version >= '3.9' pluggy==1.5.0 ; python_full_version < '3.9' pluggy==1.6.0 ; python_full_version >= '3.9' pre-commit==3.5.0 ; python_full_version < '3.9' pre-commit==4.2.0 ; python_full_version >= '3.9' pycparser==2.22 ; platform_python_implementation != 'PyPy' pycryptodome==3.23.0 pygments==2.19.1 pytest==8.3.5 pytest-cov==5.0.0 ; python_full_version < '3.9' pytest-cov==6.1.1 ; python_full_version >= '3.9' pytz==2025.2 ; python_full_version < '3.9' pyyaml==6.0.2 requests==2.32.3 roman-numerals-py==3.1.0 ; python_full_version >= '3.11' ruff==0.11.11 setuptools==75.3.2 ; python_full_version < '3.9' setuptools==80.8.0 ; python_full_version >= '3.9' shibuya==2025.4.25 snowballstemmer==3.0.1 sphinx==7.1.2 ; python_full_version < '3.9' sphinx==7.4.7 ; python_full_version == '3.9.*' sphinx==8.1.3 ; python_full_version == '3.10.*' sphinx==8.2.3 ; python_full_version >= '3.11' sphinx-contributors==0.2.7 sphinx-copybutton==0.5.2 sphinx-design==0.5.0 ; python_full_version < '3.9' sphinx-design==0.6.1 ; python_full_version >= '3.9' sphinx-intl==2.2.0 ; python_full_version < '3.9' sphinx-intl==2.3.1 ; python_full_version >= '3.9' sphinx-sitemap==2.6.0 sphinxcontrib-applehelp==1.0.4 ; python_full_version < '3.9' sphinxcontrib-applehelp==2.0.0 ; python_full_version >= '3.9' sphinxcontrib-devhelp==1.0.2 ; python_full_version < '3.9' sphinxcontrib-devhelp==2.0.0 ; python_full_version >= '3.9' sphinxcontrib-htmlhelp==2.0.1 ; python_full_version < '3.9' sphinxcontrib-htmlhelp==2.1.0 ; python_full_version >= '3.9' sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 ; python_full_version < '3.9' sphinxcontrib-qthelp==2.0.0 ; python_full_version >= '3.9' sphinxcontrib-serializinghtml==1.1.5 ; python_full_version < '3.9' sphinxcontrib-serializinghtml==2.0.0 ; python_full_version >= '3.9' tomli==2.2.1 ; python_full_version <= '3.11' typing-extensions==4.13.2 urllib3==2.2.3 ; python_full_version < '3.9' urllib3==2.4.0 ; python_full_version >= '3.9' virtualenv==20.31.2 zipp==3.20.2 ; python_full_version < '3.9' zipp==3.21.0 ; python_full_version == '3.9.*' joserfc-1.1.0/serve.py000066400000000000000000000005221501424510600146370ustar00rootroot00000000000000from livereload import Server, shell app = Server() # app.watch("src", shell("make build-docs"), delay=2) app.watch("docs/*.rst", shell("make dev-docs"), delay=2) app.watch("docs/*/*.rst", shell("make dev-docs"), delay=2) app.watch("docs/locales/zh/LC_MESSAGES/*.po", shell("make dev-docs -e lang=zh"), delay=2) app.serve(root="public") joserfc-1.1.0/setup.py000066400000000000000000000000461501424510600146540ustar00rootroot00000000000000from setuptools import setup setup() joserfc-1.1.0/sonar-project.properties000066400000000000000000000003711501424510600200470ustar00rootroot00000000000000sonar.projectKey=authlib_joserfc sonar.organization=authlib sonar.sources=src sonar.sourceEncoding=UTF-8 sonar.test.inclusions=tests/**/test_*.py sonar.python.version=3.8, 3.9, 3.10, 3.11, 3.12, 3.13 sonar.python.coverage.reportPaths=coverage.xml joserfc-1.1.0/src/000077500000000000000000000000001501424510600137315ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/000077500000000000000000000000001501424510600153645ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/__init__.py000066400000000000000000000002201501424510600174670ustar00rootroot00000000000000__version__ = "1.1.0" __homepage__ = "https://jose.authlib.org/en/" __author__ = "Hsiaoming Yang " __license__ = "BSD-3-Clause" joserfc-1.1.0/src/joserfc/_keys.py000066400000000000000000000134041501424510600170520ustar00rootroot00000000000000from __future__ import annotations import typing as t import random from .rfc7517.types import AnyKey, KeyParameters, DictKey from .rfc7518.oct_key import OctKey from .rfc7518.rsa_key import RSAKey from .rfc7518.ec_key import ECKey from .rfc8037.okp_key import OKPKey from .errors import ( MissingKeyError, InvalidKeyIdError, InvalidKeyTypeError, MissingKeyTypeError, ) from .util import to_bytes __all__ = [ "OctKey", "RSAKey", "ECKey", "OKPKey", "Key", "KeySet", "JWKRegistry", "KeySetSerialization", ] Key = t.Union[OctKey, RSAKey, ECKey, OKPKey] class JWKRegistry: """A registry for JWK to record ``joserfc`` supported key types. Normally, you would use explicit key types like ``OctKey``, ``RSAKey``; This registry provides a way to dynamically import and generate keys. For instance: .. code-block:: python from joserfc.jwk import JWKRegistry # instead of choosing which key type to use yourself, # JWKRegistry can import it automatically data = {"kty": "oct", "k": "..."} key = JWKRegistry.import_key(data) """ key_types: t.Dict[str, t.Type[Key]] = { OctKey.key_type: OctKey, RSAKey.key_type: RSAKey, ECKey.key_type: ECKey, OKPKey.key_type: OKPKey, } @classmethod def import_key(cls, data: AnyKey, key_type: str | None = None, parameters: KeyParameters | None = None) -> Key: """A class method for importing a key from bytes, string, and dict. When ``value`` is a dict, this method can tell the key type automatically, otherwise, developers SHOULD pass the ``key_type`` themselves. :param data: the key data in bytes, string, or dict. :param key_type: an optional key type in string. :param parameters: extra key parameters :return: OctKey, RSAKey, ECKey, or OKPKey """ if isinstance(data, dict) and key_type is None: if "kty" in data: key_type = data["kty"] # type: ignore[assignment] else: raise MissingKeyTypeError("Missing key type") if key_type not in cls.key_types: raise InvalidKeyTypeError(f"Invalid key type: '{key_type}'") if isinstance(data, str): data = to_bytes(data) key_cls = cls.key_types[key_type] return key_cls.import_key(data, parameters) @classmethod def generate_key( cls, key_type: str, crv_or_size: str | int | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> Key: """A class method for generating key according to the given key type. When ``key_type`` is "oct" and "RSA", the second parameter SHOULD be a key size in bits. When ``key_type`` is "EC" and "OKP", the second parameter SHOULD be a "crv" string. .. code-block:: python JWKRegistry.generate_key("RSA", 2048) JWKRegistry.generate_key("EC", "P-256") """ if key_type not in cls.key_types: raise InvalidKeyTypeError(f"Invalid key type: '{key_type}'") key_cls = cls.key_types[key_type] return key_cls.generate_key(crv_or_size, parameters, private, auto_kid) # type: ignore[arg-type] KeySetSerialization = t.TypedDict("KeySetSerialization", {"keys": t.List[DictKey]}) class KeySet: #: keys in the key set keys: list[Key] registry_cls: t.Type[JWKRegistry] = JWKRegistry algorithm_keys: t.ClassVar[t.Dict[str, list[str]]] = {} def __init__(self, keys: list[Key]): for key in keys: key.ensure_kid() self.keys = keys def __iter__(self) -> t.Iterator[Key]: return iter(self.keys) def __bool__(self) -> bool: return bool(self.keys) def __eq__(self, other: t.Any) -> bool: assert isinstance(other, KeySet) return self.keys == other.keys def as_dict(self, private: bool | None = None, **params: t.Any) -> KeySetSerialization: keys: list[DictKey] = [] for key in self.keys: # trigger key to generate kid via thumbprint key.ensure_kid() if isinstance(key, OctKey): keys.append(key.as_dict(**params)) else: keys.append(key.as_dict(private=private, **params)) return {"keys": keys} def get_by_kid(self, kid: str | None = None) -> Key: if kid is None and len(self.keys) == 1: return self.keys[0] for key in self.keys: if key.kid == kid: return key raise InvalidKeyIdError(f"No key for kid: '{kid}'") def pick_random_key(self, algorithm: str) -> t.Optional[Key]: key_types = self.algorithm_keys.get(algorithm) if key_types: keys = [k for k in self.keys if k.key_type in key_types] else: keys = self.keys if keys: return random.choice(keys) return None @classmethod def import_key_set(cls, value: KeySetSerialization, parameters: KeyParameters | None = None) -> "KeySet": keys: list[Key] = [] for data in value["keys"]: keys.append(cls.registry_cls.import_key(data, parameters=parameters)) if not keys: raise MissingKeyError("No keys to import") return cls(keys) @classmethod def generate_key_set( cls, key_type: str, crv_or_size: str | int, parameters: KeyParameters | None = None, private: bool = True, count: int = 4, ) -> "KeySet": keys: list[Key] = [] for _ in range(count): key = cls.registry_cls.generate_key(key_type, crv_or_size, parameters, private) keys.append(key) return cls(keys) joserfc-1.1.0/src/joserfc/drafts/000077500000000000000000000000001501424510600166475ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/drafts/__init__.py000066400000000000000000000000001501424510600207460ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/drafts/jwe_chacha20.py000066400000000000000000000026771501424510600214530ustar00rootroot00000000000000from __future__ import annotations from Crypto.Cipher import ChaCha20_Poly1305 from ..rfc7516.registry import JWERegistry from ..rfc7516.models import JWEEncModel __all__ = ["ChaCha20EncModel", "JWE_ENC_MODELS", "register_chaha20_poly1305"] class ChaCha20EncModel(JWEEncModel): # https://datatracker.ietf.org/doc/html/draft-amringer-jose-chacha-02#section-4 cek_size = 256 recommended = False def __init__(self, name: str, description: str, iv_size: int): self.name = name self.description = description self.iv_size = iv_size def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: """Key Encryption with AEAD_CHACHA20_POLY1305""" chacha = ChaCha20_Poly1305.new(key=cek, nonce=iv) chacha.update(aad) ciphertext, tag = chacha.encrypt_and_digest(plaintext) return ciphertext, tag def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: """Key Decryption with AEAD_CHACHA20_POLY1305.""" chacha = ChaCha20_Poly1305.new(key=cek, nonce=iv) chacha.update(aad) return chacha.decrypt_and_verify(ciphertext, tag) C20P = ChaCha20EncModel("C20P", "ChaCha20-Poly1305", 96) XC20P = ChaCha20EncModel("XC20P", "XChaCha20-Poly1305", 192) JWE_ENC_MODELS = [C20P, XC20P] def register_chaha20_poly1305() -> None: for model in JWE_ENC_MODELS: JWERegistry.register(model) joserfc-1.1.0/src/joserfc/drafts/jwe_ecdh_1pu.py000066400000000000000000000117271501424510600215660ustar00rootroot00000000000000from __future__ import annotations from ..rfc7516.models import Recipient, JWEKeyAgreement, JWEKeyWrapping, JWEEncModel from ..rfc7518.jwe_algs import ( A128KW, A192KW, A256KW, ) from ..rfc7518.ec_key import ECKey from ..rfc7518.derive_key import ( derive_key_for_concat_kdf, ) from ..rfc7518.jwe_encs import CBCHS2EncModel from ..registry import HeaderParameter from ..errors import InvalidEncryptionAlgorithmError __all__ = ["ECDH1PUAlgModel", "register_ecdh_1pu", "JWE_ALG_MODELS"] class ECDH1PUAlgModel(JWEKeyAgreement): """Key Agreement with Elliptic Curve Diffie-Hellman One-Pass Unified Model (ECDH-1PU) https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04 """ more_header_registry = { "epk": HeaderParameter("Ephemeral Public Key", "jwk", True), "apu": HeaderParameter("Agreement PartyUInfo", "str"), "apv": HeaderParameter("Agreement PartyVInfo", "str"), "skid": HeaderParameter("Sender Key ID", "str"), } key_types = ["EC", "OKP"] tag_aware = True def __init__(self, key_wrapping: JWEKeyWrapping | None): if key_wrapping is None: self.name = "ECDH-1PU" self.description = "ECDH-1PU using one-pass KDF and CEK in the Direct Key Agreement mode" self.key_size = None else: self.name = f"ECDH-1PU+{key_wrapping.name}" self.description = f"ECDH-1PU using one-pass KDF and CEK wrapped with {key_wrapping.name}" self.key_size = key_wrapping.key_size self.key_wrapping = key_wrapping def _check_enc(self, enc: JWEEncModel) -> None: # https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#section-2.1 # The AES_CBC_HMAC_SHA2 algorithms described in section 5.2 of [RFC7518] are compactly # committing and can be used with ECDH-1PU in Key Agreement with Key Wrapping mode. # Other content encryption algorithms MUST be rejected. In Direct Key Agreement # mode, any JWE content encryption algorithm MAY be used. if self.key_wrapping and not isinstance(enc, CBCHS2EncModel): description = ( "In key agreement with key wrapping mode ECDH-1PU algorithm " "only supports AES_CBC_HMAC_SHA2 family encryption algorithms" ) raise InvalidEncryptionAlgorithmError(description) def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: self._check_enc(enc) return self.__encrypt_agreed_upon_key(enc, recipient, None) def encrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes: self._check_enc(enc) return self.__encrypt_agreed_upon_key(enc, recipient, tag) def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: return self.__decrypt_agreed_upon_key(enc, recipient, None) def decrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes: return self.__decrypt_agreed_upon_key(enc, recipient, tag) def __encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes: sender_key = recipient.sender_key recipient_key = recipient.recipient_key ephemeral_key = recipient.ephemeral_key assert sender_key is not None assert recipient_key is not None assert ephemeral_key is not None sender_shared_key = sender_key.exchange_derive_key(recipient_key) ephemeral_shared_key = ephemeral_key.exchange_derive_key(recipient_key) shared_key = ephemeral_shared_key + sender_shared_key headers = recipient.headers() return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag) def __decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes | None) -> bytes: self._check_enc(enc) headers = recipient.headers() assert "epk" in headers sender_key = recipient.sender_key recipient_key = recipient.recipient_key assert sender_key is not None assert recipient_key is not None ephemeral_key = recipient_key.import_key(headers["epk"]) sender_shared_key = recipient_key.exchange_derive_key(sender_key) ephemeral_shared_key = recipient_key.exchange_derive_key(ephemeral_key) shared_key = ephemeral_shared_key + sender_shared_key return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size, tag) JWE_ALG_MODELS = [ ECDH1PUAlgModel(None), # ECDH-1PU ECDH1PUAlgModel(A128KW), # ECDH-1PU+A128KW ECDH1PUAlgModel(A192KW), # ECDH-1PU+A192KW ECDH1PUAlgModel(A256KW), # ECDH-1PU+A256KW ] def register_ecdh_1pu() -> None: from ..jwe import JWERegistry from ..jwk import KeySet for model in JWE_ALG_MODELS: JWERegistry.register(model) KeySet.algorithm_keys[model.name] = model.key_types joserfc-1.1.0/src/joserfc/errors.py000066400000000000000000000122321501424510600172520ustar00rootroot00000000000000from __future__ import annotations class JoseError(Exception): """Base Exception for all errors in joserfc.""" #: short-string error code error: str = "" #: long-string to describe this error description: str = "" def __init__(self, description: str | None = None): if description is not None: self.description = description message = "{}: {}".format(self.error, self.description) super(JoseError, self).__init__(message) class DecodeError(JoseError): """This error is designed for JWS/JWE. It is raised when deserialization and decryption fails. """ error = "decode_error" class MissingKeyError(JoseError): error = "missing_key" class UnsupportedKeyUseError(JoseError): error = "unsupported_key_use" class UnsupportedKeyAlgorithmError(JoseError): error = "unsupported_key_alg" class UnsupportedKeyOperationError(JoseError): error = "unsupported_key_operation" class InvalidKeyLengthError(JoseError): error = "invalid_key_length" class MissingKeyTypeError(JoseError): error = "missing_key_type" class InvalidKeyTypeError(JoseError): error = "invalid_key_type" class InvalidKeyIdError(JoseError): error = "invalid_key_id" class InvalidExchangeKeyError(JoseError): error = "invalid_exchange_key" description = "Invalid key for exchanging shared key" class InvalidEncryptedKeyError(JoseError): error = "invalid_encrypted_key" description = "JWE Encrypted Key value SHOULD be an empty octet sequence" class MissingAlgorithmError(JoseError): error = "missing_algorithm" description = "Missing 'alg' value in header" class ConflictAlgorithmError(JoseError): error = "conflict_algorithm" class UnsupportedAlgorithmError(JoseError): error = "unsupported_algorithm" class InvalidHeaderValueError(JoseError): error = "invalid_header_value" class UnsupportedHeaderError(JoseError): error = "unsupported_header" class MissingHeaderError(JoseError): """This error happens when the required header does not exist.""" error = "missing_header" def __init__(self, key: str): description = f"Missing '{key}' value in header" super(MissingHeaderError, self).__init__(description=description) class MissingCritHeaderError(JoseError): """This error happens when the critical header does not exist.""" error = "missing_crit_header" def __init__(self, key: str): description = f"Missing critical '{key}' value in header" super(MissingCritHeaderError, self).__init__(description=description) class MissingEncryptionError(JoseError): """This error is designed for JWE. It is raised when the 'enc' value in header is missing.""" error = "missing_encryption" description = "Missing 'enc' value in header" class BadSignatureError(JoseError): """This error is designed for JWS/JWT. It is raised when signature does not match. """ error = "bad_signature" class ExceededSizeError(JoseError): """This error is designed for DEF zip algorithm. It raised when the compressed data exceeds the maximum allowed length.""" error = "exceeded_size" class InvalidEncryptionAlgorithmError(JoseError): """This error is designed for JWE. It is raised when "enc" value does not work together with "alg" value. """ error = "invalid_encryption_algorithm" class InvalidCEKLengthError(JoseError): error = "invalid_cek_length" description = "Invalid 'cek' length" def __init__(self, cek_size: int): description = f"A key of size {cek_size} bits MUST be used" super(InvalidCEKLengthError, self).__init__(description=description) class InvalidClaimError(JoseError): """This error is designed for JWT. It raised when the claim contains invalid values or types.""" error = "invalid_claim" def __init__(self, claim: str): description = f"Invalid claim: '{claim}'" super(InvalidClaimError, self).__init__(description=description) class MissingClaimError(JoseError): """This error is designed for JWT. It raised when the required claims are missing.""" error = "missing_claim" def __init__(self, claim: str): description = f"Missing claim: '{claim}'" super(MissingClaimError, self).__init__(description=description) class InsecureClaimError(JoseError): """This error is designed for JWT. It raised when the claim contains sensitive information.""" error = "insecure_claim" def __init__(self, claim: str): description = f"Insecure claim '{claim}'" super(InsecureClaimError, self).__init__(description=description) class ExpiredTokenError(JoseError): """This error is designed for JWT. It raised when the token is expired.""" error = "expired_token" description = "The token is expired" class InvalidTokenError(JoseError): """This error is designed for JWT. It raised when the token is not valid yet.""" error = "invalid_token" description = "The token is not valid yet" class InvalidPayloadError(JoseError): """This error is designed for JWT. It raised when the payload is not a valid JSON object.""" error = "invalid_payload" joserfc-1.1.0/src/joserfc/jwe.py000066400000000000000000000240371501424510600165310ustar00rootroot00000000000000from __future__ import annotations from typing import overload from .rfc7516.types import ( GeneralJSONSerialization, FlattenedJSONSerialization, ) from .rfc7516.models import ( Recipient as Recipient, CompactEncryption as CompactEncryption, GeneralJSONEncryption as GeneralJSONEncryption, FlattenedJSONEncryption as FlattenedJSONEncryption, JWEEncModel, JWEZipModel, ) from .rfc7516.registry import ( JWERegistry as JWERegistry, default_registry, ) from .rfc7516.message import perform_encrypt, perform_decrypt from .rfc7516.compact import represent_compact, extract_compact from .rfc7516.json import ( represent_general_json, represent_flattened_json, extract_general_json, extract_flattened_json, ) from .rfc7518.jwe_algs import JWE_ALG_MODELS from .rfc7518.jwe_encs import JWE_ENC_MODELS from .rfc7518.jwe_zips import JWE_ZIP_MODELS from .jwk import Key, KeySet, ECKey, OKPKey, KeyFlexible, guess_key from .util import to_bytes from .registry import Header __all__ = [ "JWERegistry", "JWEEncModel", "JWEZipModel", "Recipient", "CompactEncryption", "GeneralJSONEncryption", "FlattenedJSONEncryption", "encrypt_compact", "decrypt_compact", "encrypt_json", "decrypt_json", "default_registry", ] def register_key_set() -> None: for _alg in JWE_ALG_MODELS: KeySet.algorithm_keys[_alg.name] = _alg.key_types def register_algorithms() -> None: for _alg in JWE_ALG_MODELS: JWERegistry.register(_alg) for _enc in JWE_ENC_MODELS: JWERegistry.register(_enc) for _zip in JWE_ZIP_MODELS: JWERegistry.register(_zip) register_key_set() register_algorithms() def encrypt_compact( protected: Header, plaintext: bytes | str, public_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> str: """Generate a JWE Compact Serialization. The JWE Compact Serialization represents encrypted content as a compact, URL-safe string. This string is:: BASE64URL(UTF8(JWE Protected Header)) || '.' || BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector) || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag) :param protected: protected header part of the JWE, in dict :param plaintext: the content (message) to be encrypted :param public_key: a public key used to encrypt the CEK :param algorithms: a list of allowed algorithms :param registry: a JWERegistry to use :param sender_key: only required when using ECDH-1PU :return: JWE Compact Serialization in bytes """ if algorithms: registry = JWERegistry(algorithms=algorithms) elif registry is None: registry = default_registry obj = CompactEncryption(protected, to_bytes(plaintext)) recipient: Recipient[Key] = Recipient(obj) key = guess_key(public_key, recipient, True) key.check_use("enc") recipient.recipient_key = key if sender_key: recipient.sender_key = _guess_sender_key(recipient, sender_key, True) obj.recipient = recipient perform_encrypt(obj, registry) out = represent_compact(obj) return out.decode("utf-8") def decrypt_compact( value: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> CompactEncryption: """Extract and validate the JWE Compact Serialization (in string, or bytes) with the given key. An JWE Compact Serialization looks like: .. code-block:: text :caption: line breaks for display purposes only OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8 1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi 6UklfCpIMfIjf7iGdXKHzg :param value: a string (or bytes) of the JWE Compact Serialization :param private_key: a flexible private key to decrypt the serialization :param algorithms: a list of allowed algorithms :param registry: a JWERegistry to use :param sender_key: only required when using ECDH-1PU :return: object of the ``CompactEncryption`` """ obj = extract_compact(to_bytes(value)) if algorithms: registry = JWERegistry(algorithms=algorithms) elif registry is None: registry = default_registry recipient = obj.recipient assert recipient is not None key = guess_key(private_key, recipient) key.check_use("enc") recipient.recipient_key = key if sender_key: recipient.sender_key = _guess_sender_key(recipient, sender_key) perform_decrypt(obj, registry) return obj @overload def encrypt_json( obj: GeneralJSONEncryption, public_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> GeneralJSONSerialization: ... @overload def encrypt_json( obj: FlattenedJSONEncryption, public_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> FlattenedJSONSerialization: ... def encrypt_json( obj: GeneralJSONEncryption | FlattenedJSONEncryption, public_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> GeneralJSONSerialization | FlattenedJSONSerialization: """Generate a JWE JSON Serialization (in dict). The JWE JSON Serialization represents encrypted content as a JSON object. This representation is neither optimized for compactness nor URL safe. When calling this method, developers MUST construct an instance of a ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object. Here is an example:: from joserfc.jwe import GeneralJSONEncryption protected = {"enc": "A128CBC-HS256"} plaintext = b"hello world" header = {"jku": "https://server.example.com/keys.jwks"} # optional shared header obj = GeneralJSONEncryption(protected, plaintext, header) # add the recipients obj.add_recipient({"kid": "alice", "alg": "RSA1_5"}) # not configured a key bob_key = OctKey.import_key("bob secret") obj.add_recipient({"kid": "bob", "alg": "A128KW"}, bob_key) :param obj: an instance of ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` :param public_key: a public key used to encrypt the CEK :param algorithms: a list of allowed algorithms :param registry: a JWERegistry to use :param sender_key: only required when using ECDH-1PU :return: JWE JSON Serialization in dict """ if algorithms: registry = JWERegistry(algorithms=algorithms) elif registry is None: registry = default_registry for recipient in obj.recipients: if sender_key and not recipient.sender_key: recipient.sender_key = _guess_sender_key(recipient, sender_key, True) if not recipient.recipient_key: assert public_key is not None key = guess_key(public_key, recipient, True) key.check_use("enc") recipient.recipient_key = key perform_encrypt(obj, registry) if isinstance(obj, GeneralJSONEncryption): return represent_general_json(obj) return represent_flattened_json(obj) def decrypt_json( data: GeneralJSONSerialization | FlattenedJSONSerialization, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWERegistry | None = None, sender_key: ECKey | OKPKey | KeySet | None = None, ) -> GeneralJSONEncryption | FlattenedJSONEncryption: """Decrypt the JWE JSON Serialization (in dict) to a ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` object. :param data: JWE JSON Serialization in dict :param private_key: a flexible private key to decrypt the CEK :param algorithms: a list of allowed algorithms :param registry: a JWERegistry to use :param sender_key: only required when using ECDH-1PU :return: an instance of ``GeneralJSONEncryption`` or ``FlattenedJSONEncryption`` """ if algorithms: registry = JWERegistry(algorithms=algorithms) elif registry is None: registry = default_registry if "recipients" in data: general_obj = extract_general_json(data) # type: ignore[arg-type] _attach_recipient_keys(general_obj.recipients, private_key, sender_key) perform_decrypt(general_obj, registry) return general_obj else: flattened_obj = extract_flattened_json(data) # type: ignore[arg-type] _attach_recipient_keys(flattened_obj.recipients, private_key, sender_key) perform_decrypt(flattened_obj, registry) return flattened_obj def _attach_recipient_keys( recipients: list[Recipient[Key]], private_key: KeyFlexible, sender_key: ECKey | OKPKey | KeySet | None = None ) -> None: for recipient in recipients: key = guess_key(private_key, recipient) key.check_use("enc") recipient.recipient_key = key if sender_key: recipient.sender_key = _guess_sender_key(recipient, sender_key) def _guess_sender_key( recipient: Recipient[Key], key: ECKey | OKPKey | KeySet, use_random: bool = False ) -> ECKey | OKPKey: if isinstance(key, KeySet): headers = recipient.headers() skid = headers.get("skid") if skid: return key.get_by_kid(skid) # type: ignore[return-value] if use_random: skey = key.pick_random_key(headers["alg"]) if skey is not None: recipient.add_header("skid", skey.kid) return skey # type: ignore[return-value] raise ValueError("Invalid key") return key joserfc-1.1.0/src/joserfc/jwk.py000066400000000000000000000070551501424510600165400ustar00rootroot00000000000000from __future__ import annotations import typing as t import warnings from ._keys import ( JWKRegistry, KeySet, Key, KeySetSerialization, ) from .rfc7517.types import AnyKey, KeyParameters from .rfc7518.oct_key import OctKey as OctKey from .rfc7518.rsa_key import RSAKey as RSAKey from .rfc7518.ec_key import ECKey as ECKey from .rfc8037.okp_key import OKPKey as OKPKey from .rfc8812 import register_secp256k1 from .registry import Header __all__ = [ "JWKRegistry", "Key", "KeyCallable", "KeyFlexible", "KeySetSerialization", "OctKey", "RSAKey", "ECKey", "OKPKey", "KeySet", "KeyBase", "GuestProtocol", "guess_key", "import_key", "generate_key", ] register_secp256k1() class GuestProtocol(t.Protocol): # pragma: no cover def headers(self) -> Header: ... def set_kid(self, kid: str) -> None: ... KeyBase = t.Union[str, bytes, Key, KeySet] KeyCallable = t.Callable[[GuestProtocol], KeyBase] KeyFlexible = t.Union[KeyBase, KeyCallable] def guess_key(key: KeyFlexible, obj: GuestProtocol, use_random: bool = False) -> Key: """Guess key from a various sources. :param key: a very flexible key :param obj: a protocol that has ``headers`` and ``set_kid`` methods :param use_random: pick a random key from key set """ _norm_key: t.Union[Key, KeySet] if callable(key): _norm_key = _normalize_key(key(obj)) else: _norm_key = _normalize_key(key) rv_key: Key if isinstance(_norm_key, KeySet): headers = obj.headers() kid = headers.get("kid") if not kid and use_random: # choose one key by random rv_key = _norm_key.pick_random_key(headers["alg"]) # type: ignore[assignment] if rv_key is None: raise ValueError("Invalid key") rv_key.ensure_kid() assert rv_key.kid is not None # for mypy obj.set_kid(rv_key.kid) else: rv_key = _norm_key.get_by_kid(kid) elif isinstance(_norm_key, (OctKey, RSAKey, ECKey, OKPKey)): rv_key = _norm_key else: raise ValueError("Invalid key") return rv_key def _normalize_key(key: KeyBase) -> Key | KeySet: if isinstance(key, (str, bytes)): # pragma: no cover warnings.warn( "Please use a Key object instead of bytes or string.", DeprecationWarning, stacklevel=2, ) return OctKey.import_key(key) return key def import_key( data: AnyKey, key_type: str | None = None, parameters: KeyParameters | None = None ) -> Key: """Importing a key from bytes, string, and dict. When ``value`` is a dict, this method can tell the key type automatically, otherwise, developers SHOULD pass the ``key_type`` themselves. :param data: the key data in bytes, string, or dict. :param key_type: an optional key type in string. :param parameters: extra key parameters :return: OctKey, RSAKey, ECKey, or OKPKey """ return JWKRegistry.import_key(data, key_type, parameters) def generate_key( key_type: str, crv_or_size: str | int | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> Key: """Generating key according to the given key type. When ``key_type`` is "oct" and "RSA", the second parameter SHOULD be a key size in bits. When ``key_type`` is "EC" and "OKP", the second parameter SHOULD be a "crv" string. """ return JWKRegistry.generate_key(key_type, crv_or_size, parameters, private, auto_kid) joserfc-1.1.0/src/joserfc/jws.py000066400000000000000000000247361501424510600165550ustar00rootroot00000000000000from __future__ import annotations from typing import overload, TypeVar, Any, Dict from .rfc7515.model import ( JWSAlgModel, HeaderMember as HeaderMember, CompactSignature as CompactSignature, GeneralJSONSignature as GeneralJSONSignature, FlattenedJSONSignature as FlattenedJSONSignature, ) from .rfc7515.registry import ( JWSRegistry as JWSRegistry, construct_registry, ) from .rfc7515.compact import ( extract_compact as extract_compact, sign_compact, verify_compact, detach_compact_content, ) from .rfc7515.json import ( sign_general_json, sign_flattened_json, verify_general_json, verify_flattened_json, extract_general_json, extract_flattened_json, detach_json_content, ) from .rfc7515.types import ( HeaderDict as HeaderDict, GeneralJSONSerialization as GeneralJSONSerialization, FlattenedJSONSerialization as FlattenedJSONSerialization, ) from .rfc7518.jws_algs import JWS_ALGORITHMS from .rfc8037.jws_eddsa import EdDSA from .rfc8812 import ES256K from .errors import BadSignatureError, MissingKeyError from .jwk import Key, KeyFlexible, KeySet, guess_key from .util import to_bytes from .registry import Header __all__ = [ "JWSAlgModel", "JWSRegistry", "HeaderDict", "HeaderMember", "CompactSignature", "GeneralJSONSignature", "FlattenedJSONSignature", "GeneralJSONSerialization", "FlattenedJSONSerialization", "serialize_compact", "deserialize_compact", "extract_compact", "validate_compact", "serialize_json", "deserialize_json", "detach_content", ] def register_key_set() -> None: for _alg in JWS_ALGORITHMS: KeySet.algorithm_keys[_alg.name] = [_alg.key_type] KeySet.algorithm_keys[EdDSA.name] = [EdDSA.key_type] KeySet.algorithm_keys[ES256K.name] = [ES256K.key_type] # register supported alg models def register_algorithms() -> None: # register alg in RFC7518 for _alg in JWS_ALGORITHMS: JWSRegistry.register(_alg) # register alg in RFC8037 JWSRegistry.register(EdDSA) # register alg in RFC8812 JWSRegistry.register(ES256K) register_key_set() register_algorithms() def serialize_compact( protected: Header, payload: bytes | str, private_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> str: """Generate a JWS Compact Serialization. The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string, per Section 7.1. .. code-block:: text BASE64URL(UTF8(JWS Protected Header)) || '.' || BASE64URL(JWS Payload) || '.' || BASE64URL(JWS Signature) :param protected: protected header part of the JWS, in dict :param payload: payload data of the JWS, in bytes :param private_key: a flexible private key to sign the signature :param algorithms: a list of allowed algorithms :param registry: a JWSRegistry to use :return: JWS in str """ if registry is None: registry = construct_registry(algorithms) registry.check_header(protected) obj = CompactSignature(protected, to_bytes(payload)) alg: JWSAlgModel = registry.get_alg(protected["alg"]) # "none" algorithm requires no key if alg.name == "none": out = sign_compact(obj, alg, None) return out.decode("utf-8") if private_key is None: raise MissingKeyError() key: Key = guess_key(private_key, obj, True) key.check_use("sig") alg.check_key_type(key) key.check_alg(protected["alg"]) out = sign_compact(obj, alg, key) return out.decode("utf-8") def validate_compact( obj: CompactSignature, public_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> bool: """Validate the JWS Compact Serialization with the given key. This method is usually used together with ``extract_compact``. :param obj: object of the JWS Compact Serialization :param public_key: a flexible public key to verify the signature :param algorithms: a list of allowed algorithms :param registry: a JWSRegistry to use """ if registry is None: registry = construct_registry(algorithms) headers = obj.headers() registry.check_header(headers) alg: JWSAlgModel = registry.get_alg(headers["alg"]) # "none" algorithm requires no key if headers["alg"] == "none": return verify_compact(obj, alg, None) if public_key is None: raise MissingKeyError() key: Key = guess_key(public_key, obj) key.check_use("sig") alg.check_key_type(key) return verify_compact(obj, alg, key) def deserialize_compact( value: bytes | str, public_key: KeyFlexible | None, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> CompactSignature: """Extract and validate the JWS Compact Serialization (in string, or bytes) with the given key. An JWE Compact Serialization looks like: .. code-block:: text :caption: line breaks for display purposes only eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9 . eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt cGxlLmNvbS9pc19yb290Ijp0cnVlfQ . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk :param value: a string (or bytes) of the JWS Compact Serialization :param public_key: a flexible public key to verify the signature :param algorithms: a list of allowed algorithms :param registry: a JWSRegistry to use :return: object of the ``CompactSignature`` """ obj = extract_compact(to_bytes(value)) if not validate_compact(obj, public_key, algorithms, registry): raise BadSignatureError() return obj @overload def serialize_json( members: list[HeaderDict], payload: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> GeneralJSONSerialization: ... @overload def serialize_json( members: HeaderDict, payload: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> FlattenedJSONSerialization: ... def serialize_json( members: HeaderDict | list[HeaderDict], payload: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> GeneralJSONSerialization | FlattenedJSONSerialization: """Generate a JWS JSON Serialization (in dict). The JWS JSON Serialization represents digitally signed or MACed content as a JSON object. This representation is neither optimized for compactness nor URL-safe. A general JWS JSON Serialization contains: payload The "payload" member MUST be present and contain the value BASE64URL(JWS Payload). signatures The "signatures" member value MUST be an array of JSON objects. Each object represents a signature or MAC over the JWS Payload and the JWS Protected Header. A flatten JWS JSON Serialization looks like: .. code-block:: text { "payload":"", "protected":"", "header":, "signature":"" } """ if registry is None: registry = construct_registry(algorithms) def find_key(obj: Any) -> Key: return guess_key(private_key, obj, True) _payload = to_bytes(payload) if isinstance(members, list): return sign_general_json(members, _payload, registry, find_key) else: return sign_flattened_json(members, _payload, registry, find_key) @overload def deserialize_json( value: GeneralJSONSerialization, public_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> GeneralJSONSignature: ... @overload def deserialize_json( value: FlattenedJSONSerialization, public_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> FlattenedJSONSignature: ... def deserialize_json( value: GeneralJSONSerialization | FlattenedJSONSerialization, public_key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None, ) -> GeneralJSONSignature | FlattenedJSONSignature: """Extract and validate the JWS (in string) with the given key. :param value: a dict of the JSON signature :param public_key: a flexible public key to verify the signature :param algorithms: a list of allowed algorithms :param registry: a JWSRegistry to use :return: object of the SignatureData :raise: ValueError or BadSignatureError """ if registry is None: registry = construct_registry(algorithms) def find_key(obj: Any) -> Key: return guess_key(public_key, obj) if "signatures" in value: general_obj = extract_general_json(value) if not verify_general_json(general_obj, registry, find_key): raise BadSignatureError() return general_obj else: flattened_obj = extract_flattened_json(value) if not verify_flattened_json(flattened_obj, registry, find_key): raise BadSignatureError() return flattened_obj DetachValue = TypeVar("DetachValue", str, Dict[str, Any]) def detach_content(value: DetachValue) -> DetachValue: """In some contexts, it is useful to integrity-protect content that is not itself contained in a JWS. This method is an implementation of https://www.rfc-editor.org/rfc/rfc7515#appendix-F It is used to detach the content of the compact and JSON serialization. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("secret") >>> encoded_text = jws.serialize_compact({"alg": "HS256"}, b"hello", key) >>> jws.detach_content(encoded_text) 'eyJhbGciOiJIUzI1NiJ9..UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A' You can also detach the JSON serialization: .. code-block:: python >>> obj = jws.serialize_json({"protected": {"alg": "HS256"}}, b"hello", key) >>> jws.detach_content(obj) { 'payload': '', 'signature': 'UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A', 'protected': 'eyJhbGciOiJIUzI1NiJ9' } """ if isinstance(value, str): return detach_compact_content(value) return detach_json_content(value) joserfc-1.1.0/src/joserfc/jwt.py000066400000000000000000000073671501424510600165570ustar00rootroot00000000000000from __future__ import annotations import json from json import JSONEncoder, JSONDecoder from typing import Type from .rfc7519.claims import convert_claims from .rfc7519.claims import Claims as Claims from .rfc7519.claims import check_sensitive_data as check_sensitive_data from .rfc7519.registry import ClaimsOption as ClaimsOption from .rfc7519.registry import JWTClaimsRegistry as JWTClaimsRegistry from .jws import ( JWSRegistry, serialize_compact, deserialize_compact, ) from .jwe import ( JWERegistry, encrypt_compact, decrypt_compact, ) from .jwk import KeyFlexible from .errors import InvalidPayloadError from .util import to_bytes from .registry import Header __all__ = [ "Claims", "Token", "ClaimsOption", "JWTClaimsRegistry", "encode", "decode", "check_sensitive_data", ] class Token: """The extracted token object, which contains ``header`` and ``claims``. :param header: the header part of the JWT :param claims: the payload part of the JWT """ def __init__(self, header: Header, claims: Claims): #: header in dict self.header = header #: payload claims in dict self.claims = claims def encode( header: Header, claims: Claims, key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | JWERegistry | None = None, encoder_cls: Type[JSONEncoder] | None = None, ) -> str: """Encode a JSON Web Token with the given header, and claims. :param header: A dict of the JWT header :param claims: A dict of the JWT claims to be encoded :param key: key used to sign the signature :param algorithms: a list of allowed algorithms :param registry: a ``JWSRegistry`` or ``JWERegistry`` to use :param encoder_cls: A JSONEncoder subclass to use """ # add ``typ`` in header _header = {"typ": "JWT", **header} payload = convert_claims(claims, encoder_cls) if isinstance(registry, JWERegistry): return encrypt_compact(_header, payload, key, algorithms, registry) else: return serialize_compact(_header, payload, key, algorithms, registry) def decode( value: bytes | str, key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | JWERegistry | None = None, decoder_cls: Type[JSONDecoder] | None = None, ) -> Token: """Decode the JSON Web Token string with the given key, and validate it with the claims requests. :param value: text of the JWT :param key: key used to verify the signature :param algorithms: a list of allowed algorithms :param registry: a ``JWSRegistry`` or ``JWERegistry`` to use :param decoder_cls: A JSONDecoder subclass to use :raise: BadSignatureError """ _value = to_bytes(value) header: Header payload: bytes if isinstance(registry, JWERegistry): header, payload = _decode_jwe(_value, key, algorithms, registry) else: header, payload = _decode_jws(_value, key, algorithms, registry) try: claims: Claims = json.loads(payload, cls=decoder_cls) except (TypeError, ValueError): raise InvalidPayloadError() return Token(header, claims) def _decode_jwe( value: bytes, key: KeyFlexible, algorithms: list[str] | None = None, registry: JWERegistry | None = None ) -> tuple[Header, bytes]: jwe_obj = decrypt_compact(value, key, algorithms, registry) assert jwe_obj.plaintext is not None return jwe_obj.headers(), jwe_obj.plaintext def _decode_jws( value: bytes, key: KeyFlexible, algorithms: list[str] | None = None, registry: JWSRegistry | None = None ) -> tuple[Header, bytes]: jws_obj = deserialize_compact(value, key, algorithms, registry) assert jws_obj.payload is not None return jws_obj.headers(), jws_obj.payload joserfc-1.1.0/src/joserfc/py.typed000066400000000000000000000000001501424510600170510ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/registry.py000066400000000000000000000152761501424510600176210ustar00rootroot00000000000000from __future__ import annotations from typing import Any, Dict, Callable, Union from .errors import ( MissingHeaderError, MissingCritHeaderError, UnsupportedHeaderError, InvalidHeaderValueError, ) Header = Dict[str, Any] def is_str(value: str) -> None: if not isinstance(value, str): raise ValueError("must be a str") def is_url(value: str) -> None: is_str(value) if not value.startswith(("http://", "https://")): raise ValueError("must be a URL") def is_int(value: int) -> None: if not isinstance(value, int): raise ValueError("must be an int") def is_bool(value: bool) -> None: if not isinstance(value, bool): raise ValueError("must be an bool") def is_list_str(values: list[str]) -> None: if not isinstance(values, list): raise ValueError("must be a list[str]") if not all(isinstance(value, str) for value in values): raise ValueError("must be a list[str]") def is_jwk(value: Dict[str, Any]) -> None: if not isinstance(value, dict): raise ValueError("must be a JWK") def in_choices(choices: list[str]) -> Callable[[Union[str, list[str]]], None]: def _is_one_of(value: str | list[str]) -> None: if isinstance(value, list): if not all(v in choices for v in value): raise ValueError(f"must be one of {choices}") elif value not in choices: raise ValueError(f"must be one of {choices}") return _is_one_of def not_support(_: Any) -> None: raise ValueError("is not supported") Validate = Callable[[Any], None] _value_validators: Dict[str, Validate] = { "str": is_str, "list[str]": is_list_str, "int": is_int, "bool": is_bool, "url": is_url, "jwk": is_jwk, "none": not_support, } class HeaderParameter: """Define the header parameter for JWS and JWE.""" def __init__(self, description: str, validate: str | Validate, required: bool = False): #: a short description of the header parameter self.description = description #: a function for validating the header parameter's value self.validate = _value_validators[validate] if isinstance(validate, str) else validate #: if this header parameter is required self.required = required #: Define header parameters for JWS and JWE HeaderRegistryDict = Dict[str, HeaderParameter] class KeyParameter: """Define the key parameter for JWK.""" def __init__(self, description: str, validate: str | Validate, private: bool | None = None, required: bool = False): #: a short description of the key parameter self.description: str = description #: a function for validating the key parameter's value self.validate = _value_validators[validate] if isinstance(validate, str) else validate #: if this key parameter for private key only self.private = private #: if this key parameter is required self.required = required class KeyOperation: def __init__(self, description: str, use: str, private: bool | None): self.description = description self.use = use self.private = private #: Define parameters for JWK KeyParameterRegistryDict = Dict[str, KeyParameter] KeyOperationRegistryDict = Dict[str, KeyOperation] #: Basic JWS header registry JWS_HEADER_REGISTRY: HeaderRegistryDict = { "alg": HeaderParameter("Algorithm", is_str, True), "jku": HeaderParameter("JWK Set URL", is_url), "jwk": HeaderParameter("JSON Web Key", is_jwk), "kid": HeaderParameter("Key ID", is_str), "x5u": HeaderParameter("X.509 URL", is_url), "x5c": HeaderParameter("X.509 Certificate Chain", is_list_str), "x5t": HeaderParameter("X.509 Certificate SHA-1 Thumbprint", is_str), "x5t#S256": HeaderParameter("X.509 Certificate SHA-256 Thumbprint", is_str), "typ": HeaderParameter("Type", is_str), "cty": HeaderParameter("Content Type", is_str), "crit": HeaderParameter("Critical", is_list_str), } #: Basic JWE header registry JWE_HEADER_REGISTRY = { "enc": HeaderParameter("Encryption Algorithm", is_str, True), "zip": HeaderParameter("Compression Algorithm", is_str), **JWS_HEADER_REGISTRY, } #: Basic JWK parameter registry JWK_PARAMETER_REGISTRY = { "kty": KeyParameter("Key Type", is_str, required=True), # This member MUST be present in a JWK. "use": KeyParameter("Public Key Use", in_choices(["sig", "enc"])), "key_ops": KeyParameter( "Key Operations", in_choices( [ "sign", "verify", "encrypt", "decrypt", "wrapKey", "unwrapKey", "deriveKey", "deriveBits", ] ), ), "alg": KeyParameter("Algorithm", is_str), "kid": KeyParameter("Key ID", is_str), "x5u": KeyParameter("X.509 URL", is_url), "x5c": KeyParameter("X.509 Certificate Chain", is_list_str), "x5t": KeyParameter("X.509 Certificate SHA-1 Thumbprint", is_str), "x5t#S256": KeyParameter("X.509 Certificate SHA-256 Thumbprint", is_str), } #: Common JWK operations #: https://www.rfc-editor.org/rfc/rfc7517#section-4.3 JWK_OPERATION_REGISTRY = { "sign": KeyOperation("compute digital signature or MAC", "sig", True), "verify": KeyOperation("verify digital signature or MAC", "sig", False), "encrypt": KeyOperation("encrypt content", "enc", False), "decrypt": KeyOperation("decrypt content and validate decryption, if applicable", "enc", True), "wrapKey": KeyOperation("encrypt key", "enc", False), "unwrapKey": KeyOperation("decrypt key and validate decryption, if applicable", "enc", True), "deriveKey": KeyOperation("derive key", "enc", False), "deriveBits": KeyOperation("derive bits not to be used as a key", "enc", None), } def check_supported_header(registry: HeaderRegistryDict, header: Header) -> None: allowed_keys = set(registry.keys()) unsupported_keys = set(header.keys()) - allowed_keys if unsupported_keys: raise UnsupportedHeaderError(f"Unsupported {unsupported_keys} in header") def validate_registry_header(registry: HeaderRegistryDict, header: Header, check_required: bool = True) -> None: for key, reg in registry.items(): if check_required and reg.required and key not in header: raise MissingHeaderError(key) if key in header: try: reg.validate(header[key]) except ValueError as error: raise InvalidHeaderValueError(f"'{key}' in header {error}") def check_crit_header(header: Header) -> None: # check crit header if "crit" in header: for k in header["crit"]: if k not in header: raise MissingCritHeaderError(k) joserfc-1.1.0/src/joserfc/rfc7515/000077500000000000000000000000001501424510600164605ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7515/__init__.py000066400000000000000000000000001501424510600205570ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7515/compact.py000066400000000000000000000042761501424510600204710ustar00rootroot00000000000000import binascii import typing as t from .model import JWSAlgModel, CompactSignature from ..errors import ( DecodeError, MissingAlgorithmError, ) from ..util import ( json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) def sign_compact(obj: CompactSignature, alg: JWSAlgModel, key: t.Any) -> bytes: header_segment = json_b64encode(obj.headers()) payload_segment = urlsafe_b64encode(obj.payload) signing_input = header_segment + b"." + payload_segment signature = urlsafe_b64encode(alg.sign(signing_input, key)) return signing_input + b"." + signature def extract_compact(value: bytes) -> CompactSignature: """Extract the JWS Compact Serialization from bytes to object. :param value: JWS in bytes :raise: DecodeError """ parts = value.split(b".") if len(parts) != 3: raise DecodeError("Invalid JSON Web Signature") header_segment, payload_segment, signature_segment = parts protected = decode_header(header_segment) try: payload = urlsafe_b64decode(payload_segment) except (TypeError, ValueError): raise DecodeError("Invalid payload") obj = CompactSignature(protected, payload) obj.segments.update( { "header": header_segment, "payload": payload_segment, "signature": signature_segment, } ) return obj def verify_compact(obj: CompactSignature, alg: JWSAlgModel, key: t.Any) -> bool: signing_input = obj.segments["header"] + b"." + obj.segments["payload"] try: sig = urlsafe_b64decode(obj.segments["signature"]) except (TypeError, ValueError): return False return alg.verify(signing_input, sig, key) def detach_compact_content(value: str) -> str: # https://www.rfc-editor.org/rfc/rfc7515#appendix-F parts = value.split(".") parts[1] = "" return ".".join(parts) def decode_header(header_segment: bytes) -> t.Dict[str, t.Any]: try: protected: t.Dict[str, t.Any] = json_b64decode(header_segment) if "alg" not in protected: raise MissingAlgorithmError() except (TypeError, ValueError): raise DecodeError("Invalid header") return protected joserfc-1.1.0/src/joserfc/rfc7515/json.py000066400000000000000000000122671501424510600200130ustar00rootroot00000000000000from __future__ import annotations import typing as t import copy from .model import ( HeaderMember, GeneralJSONSignature, FlattenedJSONSignature, ) from .types import ( HeaderDict, JSONSignatureDict, GeneralJSONSerialization, FlattenedJSONSerialization, ) from .registry import JWSRegistry from ..util import ( json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) from ..errors import DecodeError FindKey = t.Callable[[HeaderMember], t.Any] def sign_general_json( members: list[HeaderDict], payload: bytes, registry: JWSRegistry, find_key: FindKey ) -> GeneralJSONSerialization: payload_segment = urlsafe_b64encode(payload) signatures: list[JSONSignatureDict] = [ __sign_member(payload_segment, HeaderMember(**member), registry, find_key) for member in members ] return { "payload": payload_segment.decode("utf-8"), "signatures": signatures, } def sign_flattened_json( member: HeaderDict, payload: bytes, registry: JWSRegistry, find_key: FindKey ) -> FlattenedJSONSerialization: payload_segment = urlsafe_b64encode(payload) signature = __sign_member(payload_segment, HeaderMember(**member), registry, find_key) data = {"payload": payload_segment.decode("utf-8"), **signature} return data # type: ignore[return-value] def __sign_member( payload_segment: bytes, member: HeaderMember, registry: JWSRegistry, find_key: FindKey ) -> JSONSignatureDict: headers = member.headers() registry.check_header(headers) alg = registry.get_alg(headers["alg"]) key = find_key(member) key.check_use("sig") alg.check_key_type(key) if member.protected: protected_segment = json_b64encode(member.protected) else: protected_segment = b"" signing_input = b".".join([protected_segment, payload_segment]) signature = urlsafe_b64encode(alg.sign(signing_input, key)) rv: JSONSignatureDict = {"signature": signature.decode("utf-8")} if member.protected: rv["protected"] = protected_segment.decode("utf-8") if member.header: rv["header"] = member.header return rv def extract_general_json(value: GeneralJSONSerialization) -> GeneralJSONSignature: payload_segment: bytes = value["payload"].encode("utf-8") try: payload = urlsafe_b64decode(payload_segment) except (TypeError, ValueError): raise DecodeError("Invalid payload") signatures: list[JSONSignatureDict] = value["signatures"] members = [__signature_to_member(sig) for sig in signatures] obj = GeneralJSONSignature(members, payload) obj.signatures = signatures obj.segments = {"payload": payload_segment} return obj def extract_flattened_json(value: FlattenedJSONSerialization) -> FlattenedJSONSignature: payload_segment: bytes = value["payload"].encode("utf-8") try: payload = urlsafe_b64decode(payload_segment) except (TypeError, ValueError): raise DecodeError("Invalid payload") _sig: JSONSignatureDict = {"signature": value["signature"]} if "protected" in value: _sig["protected"] = value["protected"] if "header" in value: _sig["header"] = value["header"] member = __signature_to_member(_sig) obj = FlattenedJSONSignature(member, payload) obj.signature = _sig obj.segments = {"payload": payload_segment} return obj def __signature_to_member(sig: JSONSignatureDict) -> HeaderMember: member = HeaderMember() if "protected" in sig: protected_segment = sig["protected"] member.protected = json_b64decode(protected_segment) if "header" in sig: member.header = sig["header"] return member def verify_general_json(obj: GeneralJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool: payload_segment = obj.segments["payload"] for index, signature in enumerate(obj.signatures): member = obj.members[index] if not verify_signature(member, signature, payload_segment, registry, find_key): return False return True def verify_flattened_json(obj: FlattenedJSONSignature, registry: JWSRegistry, find_key: FindKey) -> bool: payload_segment = obj.segments["payload"] assert obj.signature is not None return verify_signature(obj.member, obj.signature, payload_segment, registry, find_key) def verify_signature( member: HeaderMember, signature: JSONSignatureDict, payload_segment: bytes, registry: JWSRegistry, find_key: FindKey ) -> bool: headers = member.headers() registry.check_header(headers) alg = registry.get_alg(headers["alg"]) key = find_key(member) key.check_use("sig") alg.check_key_type(key) if "protected" in signature: protected_segment = signature["protected"].encode("utf-8") else: protected_segment = b"" sig = urlsafe_b64decode(signature["signature"].encode("utf-8")) signing_input = b".".join([protected_segment, payload_segment]) return alg.verify(signing_input, sig, key) def detach_json_content(value: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: # https://www.rfc-editor.org/rfc/rfc7515#appendix-F rv = copy.deepcopy(value) # don't alter original value if "payload" in rv: del rv["payload"] return rv joserfc-1.1.0/src/joserfc/rfc7515/model.py000066400000000000000000000070211501424510600201320ustar00rootroot00000000000000from __future__ import annotations from typing import Any, ClassVar from abc import ABCMeta, abstractmethod from .types import SegmentsDict, JSONSignatureDict from ..errors import InvalidKeyTypeError from ..registry import Header class HeaderMember: """A header member of the JSON signature. It is combined with protected header, and unprotected header. """ def __init__(self, protected: Header | None = None, header: Header | None = None): #: protected header self.protected = protected #: unprotected header self.header = header def headers(self) -> Header: rv: Header = {} if self.protected: rv.update(self.protected) if self.header: rv.update(self.header) return rv def set_kid(self, kid: str) -> None: if self.header is None: self.header = {} self.header["kid"] = kid class CompactSignature: """JSON Web Signature object for compact mode. This object is used to represent the JWS instance. """ def __init__(self, protected: Header, payload: bytes): self.protected = protected self.payload = payload self.segments: SegmentsDict = {} def headers(self) -> Header: return self.protected def set_kid(self, kid: str) -> None: self.protected["kid"] = kid class FlattenedJSONSignature: """JSON Signature object that represents a flattened JSON serialization.""" #: mark it as flattened flattened: ClassVar[bool] = True def __init__(self, member: HeaderMember, payload: bytes): #: the only header member self.member = member #: payload content self.payload = payload self.signature: JSONSignatureDict | None = None self.segments: SegmentsDict = {} @property def members(self) -> list[HeaderMember]: return [self.member] def headers(self) -> Header: return self.member.headers() class GeneralJSONSignature: """JSON Signature object that represents a general JSON serialization.""" #: mark it as not flattened (general) flattened: ClassVar[bool] = False def __init__(self, members: list[HeaderMember], payload: bytes): #: a list of header members self.members = members #: payload content self.payload = payload self.signatures: list[JSONSignatureDict] = [] self.segments: SegmentsDict = {} class JWSAlgModel(object, metaclass=ABCMeta): """Interface for JWS algorithm. JWA specification (RFC7518) SHOULD implement the algorithms for JWS with this base implementation. """ name: str description: str recommended: bool = False key_type = "oct" algorithm_type = "JWS" algorithm_location = "sig" def check_key_type(self, key: Any) -> None: if key.key_type != self.key_type: raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires '{self.key_type}' key") @abstractmethod def sign(self, msg: bytes, key: Any) -> bytes: """Sign the text msg with a private/sign key. :param msg: message bytes to be signed :param key: private key to sign the message :return: bytes """ @abstractmethod def verify(self, msg: bytes, sig: bytes, key: Any) -> bool: """Verify the signature of text msg with a public/verify key. :param msg: message bytes to be signed :param sig: result signature to be compared :param key: public key to verify the signature :return: boolean """ joserfc-1.1.0/src/joserfc/rfc7515/registry.py000066400000000000000000000054651501424510600207140ustar00rootroot00000000000000from __future__ import annotations from typing import Dict from .model import JWSAlgModel from ..errors import UnsupportedAlgorithmError from ..registry import ( JWS_HEADER_REGISTRY, Header, HeaderRegistryDict, validate_registry_header, check_crit_header, check_supported_header, ) class JWSRegistry: """A registry for JSON Web Signature to keep all the supported algorithms. An instance of ``JWSRegistry`` is usually used together with methods in ``joserfc.jws``. :param header_registry: extra header parameters registry :param algorithms: allowed algorithms to be used :param strict_check_header: only allow header key in the registry to be used """ default_header_registry: HeaderRegistryDict = JWS_HEADER_REGISTRY algorithms: Dict[str, JWSAlgModel] = {} recommended: list[str] = [] def __init__( self, header_registry: HeaderRegistryDict | None = None, algorithms: list[str] | None = None, strict_check_header: bool = True, ): self.header_registry: HeaderRegistryDict = {} self.header_registry.update(self.default_header_registry) if header_registry is not None: self.header_registry.update(header_registry) self.allowed = algorithms self.strict_check_header = strict_check_header @classmethod def register(cls, alg: JWSAlgModel) -> None: """Register a given JWS algorithm instance to the registry.""" cls.algorithms[alg.name] = alg if alg.recommended: cls.recommended.append(alg.name) def get_alg(self, name: str) -> JWSAlgModel: """Get the allowed algorithm instance of the given name. :param name: value of the ``alg``, e.g. ``HS256``, ``RS256`` """ if name not in self.algorithms: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not supported") if self.allowed: if name not in self.allowed: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not allowed") else: if name not in self.recommended: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not recommended") return self.algorithms[name] def check_header(self, header: Header) -> None: """Check and validate the fields in header part of a JWS object.""" check_crit_header(header) validate_registry_header(self.header_registry, header) if self.strict_check_header: check_supported_header(self.header_registry, header) #: default JWS registry default_registry = JWSRegistry() def construct_registry(algorithms: list[str] | None = None) -> JWSRegistry: if algorithms: registry = JWSRegistry(algorithms=algorithms) else: registry = default_registry return registry joserfc-1.1.0/src/joserfc/rfc7515/types.py000066400000000000000000000013641501424510600202020ustar00rootroot00000000000000import typing as t from ..registry import Header __all__ = [ "SegmentsDict", "HeaderDict", "JSONSignatureDict", "GeneralJSONSerialization", "FlattenedJSONSerialization", ] class SegmentsDict(t.TypedDict, total=False): header: bytes payload: bytes signature: bytes class HeaderDict(t.TypedDict, total=False): protected: Header header: Header class JSONSignatureDict(t.TypedDict, total=False): protected: str header: Header signature: str @t.final class GeneralJSONSerialization(t.TypedDict): payload: str signatures: t.List[JSONSignatureDict] @t.final class FlattenedJSONSerialization(t.TypedDict, total=False): payload: str protected: str header: Header signature: str joserfc-1.1.0/src/joserfc/rfc7516/000077500000000000000000000000001501424510600164615ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7516/__init__.py000066400000000000000000000000001501424510600205600ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7516/compact.py000066400000000000000000000035061501424510600204650ustar00rootroot00000000000000from .models import CompactEncryption, Recipient from .._keys import Key from ..errors import ( MissingAlgorithmError, MissingEncryptionError, DecodeError, ) from ..util import ( json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) def represent_compact(obj: CompactEncryption) -> bytes: assert obj.recipient is not None encrypted_key = obj.recipient.encrypted_key assert encrypted_key is not None return b".".join( [ obj.base64_segments["aad"], urlsafe_b64encode(encrypted_key), obj.base64_segments["iv"], obj.base64_segments["ciphertext"], obj.base64_segments["tag"], ] ) def extract_compact(value: bytes) -> CompactEncryption: parts = value.split(b".") if len(parts) != 5: raise ValueError("Invalid JSON Web Encryption") header_segment, ek_segment, iv_segment, ciphertext_segment, tag_segment = parts try: protected = json_b64decode(header_segment) if "alg" not in protected: raise MissingAlgorithmError() if "enc" not in protected: raise MissingEncryptionError() except (TypeError, ValueError): raise DecodeError("Invalid header") obj = CompactEncryption(protected) obj.base64_segments.update( { "aad": header_segment, "iv": iv_segment, "ciphertext": ciphertext_segment, "tag": tag_segment, } ) obj.bytes_segments.update( { "iv": urlsafe_b64decode(iv_segment), "ciphertext": urlsafe_b64decode(ciphertext_segment), "tag": urlsafe_b64decode(tag_segment), } ) recipient: Recipient[Key] = Recipient(obj) recipient.encrypted_key = urlsafe_b64decode(ek_segment) obj.recipient = recipient return obj joserfc-1.1.0/src/joserfc/rfc7516/json.py000066400000000000000000000076621501424510600200170ustar00rootroot00000000000000import typing as t from .models import ( BaseJSONEncryption, GeneralJSONEncryption, FlattenedJSONEncryption, Recipient, ) from .types import ( JSONRecipientDict, GeneralJSONSerialization, FlattenedJSONSerialization, ) from ..registry import Header from ..util import ( to_bytes, to_str, json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) from .._keys import Key def represent_general_json(obj: GeneralJSONEncryption) -> GeneralJSONSerialization: data = __represent_json_serialization(obj) recipients = [] for recipient in obj.recipients: item: JSONRecipientDict = {} if recipient.header: item["header"] = recipient.header if recipient.encrypted_key: item["encrypted_key"] = to_str(urlsafe_b64encode(recipient.encrypted_key)) recipients.append(item) data["recipients"] = recipients return data # type: ignore[no-any-return] def represent_flattened_json(obj: FlattenedJSONEncryption) -> FlattenedJSONSerialization: data = __represent_json_serialization(obj) recipient = obj.recipients[0] assert recipient is not None if recipient.header: data["header"] = recipient.header if recipient.encrypted_key: data["encrypted_key"] = to_str(urlsafe_b64encode(recipient.encrypted_key)) return data # type: ignore[no-any-return] def __represent_json_serialization(obj: BaseJSONEncryption): # type: ignore[no-untyped-def] data: t.Dict[str, t.Union[str, Header, t.List[Header]]] = { "protected": to_str(json_b64encode(obj.protected)), "iv": to_str(obj.base64_segments["iv"]), "ciphertext": to_str(obj.base64_segments["ciphertext"]), "tag": to_str(obj.base64_segments["tag"]), } if obj.aad: data["aad"] = to_str(urlsafe_b64encode(obj.aad)) if obj.unprotected: data["unprotected"] = obj.unprotected return data def extract_general_json(data: GeneralJSONSerialization) -> GeneralJSONEncryption: protected = json_b64decode(data["protected"]) unprotected = data.get("unprotected") base64_segments, bytes_segments, aad = __extract_segments(data) obj = GeneralJSONEncryption(protected, None, unprotected, aad) obj.base64_segments = base64_segments obj.bytes_segments = bytes_segments for item in data["recipients"]: recipient: Recipient[Key] = Recipient(obj, item.get("header")) if "encrypted_key" in item: recipient.encrypted_key = urlsafe_b64decode(to_bytes(item["encrypted_key"])) obj.recipients.append(recipient) return obj def extract_flattened_json(data: FlattenedJSONSerialization) -> FlattenedJSONEncryption: protected = json_b64decode(data["protected"]) unprotected = data.get("unprotected") base64_segments, bytes_segments, aad = __extract_segments(data) obj = FlattenedJSONEncryption(protected, None, unprotected, aad) obj.base64_segments = base64_segments obj.bytes_segments = bytes_segments recipient: Recipient[Key] = Recipient(obj, data.get("header")) if "encrypted_key" in data: recipient.encrypted_key = urlsafe_b64decode(to_bytes(data["encrypted_key"])) obj.recipients.append(recipient) return obj def __extract_segments( data: t.Union[GeneralJSONSerialization, FlattenedJSONSerialization], ) -> t.Tuple[t.Dict[str, bytes], t.Dict[str, bytes], t.Optional[bytes]]: base64_segments: t.Dict[str, bytes] = { "iv": to_bytes(data["iv"]), "ciphertext": to_bytes(data["ciphertext"]), "tag": to_bytes(data["tag"]), } bytes_segments: t.Dict[str, bytes] = { "iv": urlsafe_b64decode(base64_segments["iv"]), "ciphertext": urlsafe_b64decode(base64_segments["ciphertext"]), "tag": urlsafe_b64decode(base64_segments["tag"]), } if "aad" in data: aad = urlsafe_b64decode(to_bytes(data["aad"])) else: aad = None return base64_segments, bytes_segments, aad joserfc-1.1.0/src/joserfc/rfc7516/message.py000066400000000000000000000236351501424510600204700ustar00rootroot00000000000000import typing as t from .models import ( CompactEncryption, BaseJSONEncryption, GeneralJSONEncryption, FlattenedJSONEncryption, Recipient, JWEAlgModel, JWEEncModel, JWEKeyAgreement, JWEDirectEncryption, JWEKeyEncryption, JWEKeyWrapping, ) from .registry import JWERegistry from ..errors import ( JoseError, DecodeError, InvalidCEKLengthError, InvalidEncryptedKeyError, InvalidExchangeKeyError, ConflictAlgorithmError, ) from ..util import ( json_b64encode, urlsafe_b64encode, ) EncryptionData = t.Union[CompactEncryption, GeneralJSONEncryption, FlattenedJSONEncryption] def perform_encrypt(obj: EncryptionData, registry: JWERegistry) -> None: enc = registry.get_enc(obj.protected["enc"]) cek, delayed_tasks = pre_encrypt_recipients(enc, obj.recipients, registry) # Step 9, Generate a random JWE Initialization Vector of the correct size # for the content encryption algorithm (if required for the algorithm); # otherwise, let the JWE Initialization Vector be the empty octet sequence. iv = enc.generate_iv() # Step 10, Compute the encoded Initialization Vector value # BASE64URL(JWE Initialization Vector). obj.base64_segments["iv"] = urlsafe_b64encode(iv) # Step 11, If a "zip" parameter was included, compress the plaintext using # the specified compression algorithm and let M be the octet sequence # representing the compressed plaintext; otherwise, let M be the octet # sequence representing the plaintext. assert obj.plaintext is not None plaintext: bytes if "zip" in obj.protected: zip_ = registry.get_zip(obj.protected["zip"]) plaintext = zip_.compress(obj.plaintext) else: plaintext = obj.plaintext # Step 13, Compute the Encoded Protected Header value BASE64URL(UTF8(JWE Protected Header)). aad = json_b64encode(obj.protected) # Step 14, 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)). if isinstance(obj, BaseJSONEncryption) and obj.aad: aad = aad + b"." + urlsafe_b64encode(obj.aad) obj.base64_segments["aad"] = aad # encrypting plaintext ciphertext, tag = enc.encrypt(plaintext, cek, iv, aad) # delay encrypting every recipient post_encrypt_recipients(enc, delayed_tasks, cek, tag) obj.base64_segments["ciphertext"] = urlsafe_b64encode(ciphertext) obj.base64_segments["tag"] = urlsafe_b64encode(tag) def perform_decrypt(obj: EncryptionData, registry: JWERegistry) -> None: try: _perform_decrypt(obj, registry) except InvalidExchangeKeyError as error: raise DecodeError(error.description) def _perform_decrypt(obj: EncryptionData, registry: JWERegistry) -> None: enc = registry.get_enc(obj.protected["enc"]) iv = obj.bytes_segments["iv"] enc.check_iv(iv) tag = obj.bytes_segments["tag"] ciphertext = obj.bytes_segments["ciphertext"] cek_set = set() for recipient in obj.recipients: headers = recipient.headers() registry.check_header(headers, True) # Step 6, Determine the Key Management Mode employed by the algorithm # specified by the "alg" (algorithm) Header Parameter. alg = registry.get_alg(headers["alg"]) try: cek = decrypt_recipient(alg, enc, recipient, tag) cek_set.add(cek) except (AssertionError, JoseError) as error: if registry.verify_all_recipients: raise error if not cek_set: raise DecodeError("Invalid recipients") if len(cek_set) > 1: # pragma: no cover raise DecodeError("Multiple 'cek' found") cek = cek_set.pop() if len(cek) * 8 != enc.cek_size: # pragma: no cover raise InvalidCEKLengthError(enc.cek_size) aad = json_b64encode(obj.protected) if isinstance(obj, BaseJSONEncryption) and obj.aad: aad = aad + b"." + urlsafe_b64encode(obj.aad) msg = enc.decrypt(ciphertext, tag, cek, iv, aad) if "zip" in obj.protected: zip_ = registry.get_zip(obj.protected["zip"]) obj.plaintext = zip_.decompress(msg) else: obj.plaintext = msg def pre_encrypt_recipients( enc: JWEEncModel, recipients: t.List[Recipient[t.Any]], registry: JWERegistry ) -> t.Tuple[bytes, t.List[t.Tuple[JWEKeyAgreement, Recipient[t.Any]]]]: cek: bytes = b"" delayed_tasks: t.List[t.Tuple[JWEKeyAgreement, Recipient[t.Any]]] = [] for recipient in recipients: alg = __prepare_recipient_algorithm(recipient, registry) if alg.direct_mode: if len(recipients) > 1: raise ConflictAlgorithmError(f"Algorithm {alg.name} SHOULD have 1 recipient only") cek = __pre_encrypt_direct_mode(alg, enc, recipient) else: if not cek: # 2. When Key Wrapping, Key Encryption, or Key Agreement with Key # Wrapping are employed, generate a random CEK value. See RFC # 4086 [RFC4086] for considerations on generating random values. # The CEK MUST have a length equal to that required for the # content encryption algorithm. cek = enc.generate_cek() if isinstance(alg, JWEKeyAgreement): delayed_tasks.append((alg, recipient)) else: # 4. When Key Wrapping, or Key Encryption are employed, encrypt the CEK # to the recipient and let the result be the JWE Encrypted Key. assert isinstance(alg, (JWEKeyWrapping, JWEKeyEncryption)) recipient.encrypted_key = alg.encrypt_cek(cek, recipient) return cek, delayed_tasks def __prepare_recipient_algorithm(recipient: Recipient[t.Any], registry: JWERegistry) -> JWEAlgModel: headers = recipient.headers() registry.check_header(headers) # 1. Determine the Key Management Mode employed by the algorithm used # to determine the Content Encryption Key value. (This is the # algorithm recorded in the "alg" (algorithm) Header Parameter of # the resulting JWE.) alg = registry.get_alg(headers["alg"]) if isinstance(alg, JWEKeyAgreement): alg.prepare_ephemeral_key(recipient) return alg def __pre_encrypt_direct_mode(alg: JWEAlgModel, enc: JWEEncModel, recipient: Recipient[t.Any]) -> bytes: cek: bytes if isinstance(alg, JWEKeyAgreement): # 3. When Direct Key Agreement is employed, # let the CEK be the agreed upon key. cek = alg.encrypt_agreed_upon_key(enc, recipient) if len(cek) * 8 != enc.cek_size: # pragma: no cover raise InvalidCEKLengthError(enc.cek_size) else: # 6. When Direct Encryption is employed, let the CEK be the shared # symmetric key. assert isinstance(alg, JWEDirectEncryption) cek = alg.compute_cek(enc.cek_size, recipient) # 5. When Direct Key Agreement or Direct Encryption are employed, let # the JWE Encrypted Key be the empty octet sequence. recipient.encrypted_key = b"" return cek def post_encrypt_recipients( enc: JWEEncModel, tasks: t.List[t.Tuple[JWEKeyAgreement, Recipient[t.Any]]], cek: bytes, tag: bytes ) -> None: for alg, recipient in tasks: if alg.tag_aware: agreed_upon_key = alg.encrypt_agreed_upon_key_with_tag(enc, recipient, tag) else: agreed_upon_key = alg.encrypt_agreed_upon_key(enc, recipient) # 4. When Key Agreement with Key Wrapping is employed, encrypt the CEK # to the recipient and let the result be the JWE Encrypted Key. recipient.encrypted_key = alg.wrap_cek_with_auk(cek, agreed_upon_key) def decrypt_recipient(alg: JWEAlgModel, enc: JWEEncModel, recipient: Recipient[t.Any], tag: bytes) -> bytes: cek: bytes if alg.direct_mode: # 10. When Direct Key Agreement or Direct Encryption are employed, # verify that the JWE Encrypted Key value is an empty octet # sequence. if recipient.encrypted_key: # pragma: no cover raise InvalidEncryptedKeyError() if isinstance(alg, JWEKeyAgreement): # 8. When Direct Key Agreement is employed, let the CEK be the agreed upon key. cek = alg.decrypt_agreed_upon_key(enc, recipient) else: # 11. When Direct Encryption is employed, let the CEK be the shared # symmetric key. assert isinstance(alg, JWEDirectEncryption) cek = alg.compute_cek(enc.cek_size, recipient) elif isinstance(alg, JWEKeyAgreement): agreed_upon_key: bytes if alg.tag_aware: agreed_upon_key = alg.decrypt_agreed_upon_key_with_tag(enc, recipient, tag) else: agreed_upon_key = alg.decrypt_agreed_upon_key(enc, recipient) # 8. When Key Agreement with Key Wrapping is employed, the agreed upon key # will be used to decrypt the JWE Encrypted Key. assert recipient.encrypted_key is not None cek = alg.unwrap_cek_with_auk(recipient.encrypted_key, agreed_upon_key) else: # 9. 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. assert isinstance(alg, (JWEKeyWrapping, JWEKeyEncryption)) cek = alg.decrypt_cek(recipient) return cek joserfc-1.1.0/src/joserfc/rfc7516/models.py000066400000000000000000000242001501424510600203140ustar00rootroot00000000000000from __future__ import annotations import typing as t import secrets from abc import ABCMeta, abstractmethod from ..registry import Header, HeaderRegistryDict from ..errors import InvalidKeyTypeError, InvalidKeyLengthError from .._keys import Key, ECKey, OctKey KeyType = t.TypeVar("KeyType") class Recipient(t.Generic[KeyType]): def __init__( self, parent: t.Union["CompactEncryption", "GeneralJSONEncryption", "FlattenedJSONEncryption"], header: Header | None = None, recipient_key: KeyType | None = None, ): self.__parent = parent self.header = header self.recipient_key = recipient_key self.sender_key: t.Optional[KeyType] = None self.encrypted_key: t.Optional[bytes] = None self.ephemeral_key: t.Optional[KeyType] = None def headers(self) -> Header: rv: Header = {} rv.update(self.__parent.protected) if isinstance(self.__parent, BaseJSONEncryption) and self.__parent.unprotected: rv.update(self.__parent.unprotected) if self.header: rv.update(self.header) return rv def add_header(self, k: str, v: t.Any) -> None: if isinstance(self.__parent, CompactEncryption): self.__parent.protected.update({k: v}) elif self.header: self.header.update({k: v}) else: self.header = {k: v} def set_kid(self, kid: str) -> None: self.add_header("kid", kid) class CompactEncryption: """An object to represent the JWE Compact Serialization. It is usually returned by ``decrypt_compact`` method. """ def __init__(self, protected: Header, plaintext: bytes | None = None): #: protected header in dict self.protected = protected #: the plaintext in bytes self.plaintext = plaintext self.recipient: Recipient[t.Any] | None = None self.bytes_segments: t.Dict[str, bytes] = {} # store the decoded segments self.base64_segments: t.Dict[str, bytes] = {} # store the encoded segments def headers(self) -> Header: return self.protected def attach_recipient(self, key: Key, header: Header | None = None) -> None: """Add a recipient to the JWE Compact Serialization. Please add a key that comply with the given "alg" value. :param key: an instance of a key, e.g. (OctKey, RSAKey, ECKey, and etc) :param header: extra header in dict """ recipient = Recipient(self, None, key) if header: self.protected.update(header) self.recipient = recipient @property def recipients(self) -> list[Recipient[t.Any]]: if self.recipient is not None: return [self.recipient] return [] class BaseJSONEncryption(metaclass=ABCMeta): #: represents if the object is in flatten syntax flattened: t.ClassVar[bool] #: protected header in dict protected: Header #: the plaintext in bytes plaintext: t.Optional[bytes] #: unprotected header in dict unprotected: t.Optional[Header] #: an optional additional authenticated data aad: t.Optional[bytes] #: a list of recipients recipients: t.List[Recipient[t.Any]] def __init__( self, protected: Header, plaintext: bytes | None = None, unprotected: Header | None = None, aad: bytes | None = None, ): self.protected = protected self.plaintext = plaintext self.unprotected = unprotected self.aad = aad self.recipients = [] self.bytes_segments: t.Dict[str, bytes] = {} # store the decoded segments self.base64_segments: t.Dict[str, bytes] = {} # store the encoded segments @abstractmethod def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None: """Add a recipient to the JWE JSON Serialization. Please add a key that comply with the "alg" to this recipient. :param header: recipient's own (unprotected) header :param key: an instance of a key, e.g. (OctKey, RSAKey, ECKey, and etc) """ class GeneralJSONEncryption(BaseJSONEncryption): """An object to represent the JWE General JSON Serialization. It is used by ``encrypt_json``, and it is usually returned by ``decrypt_json`` method. To construct an object of ``GeneralJSONEncryption``: .. code-block:: python protected = {"enc": "A128CBC-HS256"} plaintext = b"hello world" obj = GeneralJSONEncryption(protected, plaintext) # then add each recipient obj.add_recipient({"alg": "A128KW"}) """ flattened = False def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None: recipient = Recipient(self, header, key) self.recipients.append(recipient) class FlattenedJSONEncryption(BaseJSONEncryption): """An object to represent the JWE Flattened JSON Serialization. It is used by ``encrypt_json``, and it is usually returned by ``decrypt_json`` method. To construct an object of ``FlattenedJSONEncryption``: .. code-block:: python protected = {"enc": "A128CBC-HS256"} plaintext = b"hello world" obj = FlattenedJSONEncryption(protected, plaintext) # then add each recipient obj.add_recipient({"alg": "A128KW"}) """ flattened = True def add_recipient(self, header: Header | None = None, key: Key | None = None) -> None: self.recipients = [Recipient(self, header, key)] class JWEEncModel(object, metaclass=ABCMeta): name: str description: str recommended: bool = False algorithm_type: t.Literal["JWE"] = "JWE" algorithm_location: t.Literal["enc"] = "enc" iv_size: int cek_size: int def generate_cek(self) -> bytes: return secrets.token_bytes(self.cek_size // 8) def generate_iv(self) -> bytes: return secrets.token_bytes(self.iv_size // 8) def check_iv(self, iv: bytes) -> bytes: if len(iv) * 8 != self.iv_size: # pragma: no cover raise ValueError("Invalid 'iv' size") return iv @abstractmethod def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: pass @abstractmethod def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: pass class JWEZipModel(object, metaclass=ABCMeta): name: str description: str recommended: bool = True algorithm_type: t.Literal["JWE"] = "JWE" algorithm_location: t.Literal["zip"] = "zip" @abstractmethod def compress(self, s: bytes) -> bytes: pass @abstractmethod def decompress(self, s: bytes) -> bytes: pass class KeyManagement: name: str description: str recommended: bool = False key_size: t.Optional[int] = None key_types: t.List[str] algorithm_type: t.Literal["JWE"] = "JWE" algorithm_location: t.Literal["alg"] = "alg" more_header_registry: HeaderRegistryDict = {} @property def direct_mode(self) -> bool: return self.key_size is None def check_key_type(self, key: Key) -> None: if key.key_type not in self.key_types: raise InvalidKeyTypeError() def prepare_recipient_header(self, recipient: Recipient[t.Any]) -> None: raise NotImplementedError() class JWEDirectEncryption(KeyManagement, metaclass=ABCMeta): key_types = ["oct"] @abstractmethod def compute_cek(self, size: int, recipient: Recipient[OctKey]) -> bytes: pass class JWEKeyEncryption(KeyManagement, metaclass=ABCMeta): @property def direct_mode(self) -> bool: return False @abstractmethod def encrypt_cek(self, cek: bytes, recipient: Recipient[t.Any]) -> bytes: pass @abstractmethod def decrypt_cek(self, recipient: Recipient[t.Any]) -> bytes: pass class JWEKeyWrapping(KeyManagement, metaclass=ABCMeta): key_size: int key_types = ["oct"] @property def direct_mode(self) -> bool: return False def check_op_key(self, op_key: bytes) -> None: if len(op_key) * 8 != self.key_size: raise InvalidKeyLengthError(f"A key of size {self.key_size} bits MUST be used") @abstractmethod def wrap_cek(self, cek: bytes, key: bytes) -> bytes: pass @abstractmethod def unwrap_cek(self, ek: bytes, key: bytes) -> bytes: pass @abstractmethod def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes: pass @abstractmethod def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes: pass class JWEKeyAgreement(KeyManagement, metaclass=ABCMeta): key_types = ["EC", "OKP"] tag_aware: bool = False key_wrapping: t.Optional[JWEKeyWrapping] def prepare_ephemeral_key(self, recipient: Recipient[ECKey]) -> None: recipient_key = recipient.recipient_key assert recipient_key is not None self.check_key_type(recipient_key) if recipient.ephemeral_key is None: ephemeral_key = recipient_key.generate_key(recipient_key.curve_name, private=True) recipient.ephemeral_key = ephemeral_key recipient.add_header("epk", recipient.ephemeral_key.as_dict(private=False)) @abstractmethod def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: pass @abstractmethod def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: pass def wrap_cek_with_auk(self, cek: bytes, key: bytes) -> bytes: assert self.key_wrapping is not None return self.key_wrapping.wrap_cek(cek, key) def unwrap_cek_with_auk(self, ek: bytes, key: bytes) -> bytes: assert self.key_wrapping is not None return self.key_wrapping.unwrap_cek(ek, key) def encrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes: raise NotImplementedError() def decrypt_agreed_upon_key_with_tag(self, enc: JWEEncModel, recipient: Recipient[ECKey], tag: bytes) -> bytes: raise NotImplementedError() JWEAlgModel = t.Union[JWEKeyEncryption, JWEKeyWrapping, JWEKeyAgreement, JWEDirectEncryption] joserfc-1.1.0/src/joserfc/rfc7516/registry.py000066400000000000000000000103331501424510600207030ustar00rootroot00000000000000from __future__ import annotations import typing as t from .models import JWEAlgModel, JWEEncModel, JWEZipModel from ..errors import UnsupportedAlgorithmError from ..registry import ( Header, HeaderRegistryDict, JWE_HEADER_REGISTRY, check_supported_header, validate_registry_header, check_crit_header, ) JWEAlgorithm = t.Union[JWEAlgModel, JWEEncModel, JWEZipModel] AlgorithmsDict = t.TypedDict( "AlgorithmsDict", { "alg": t.Dict[str, JWEAlgModel], "enc": t.Dict[str, JWEEncModel], "zip": t.Dict[str, JWEZipModel], }, ) class JWERegistry: """A registry for JSON Web Encryption to keep all the supported algorithms. An instance of ``JWERegistry`` is usually used together with methods in ``joserfc.jwe``. :param header_registry: extra header parameters registry :param algorithms: allowed algorithms to be used :param verify_all_recipients: validating all recipients in a JSON serialization :param strict_check_header: only allow header key in the registry to be used """ algorithms: t.ClassVar[AlgorithmsDict] = { "alg": {}, "enc": {}, "zip": {}, } recommended: t.ClassVar[t.List[str]] = [] def __init__( self, header_registry: t.Optional[HeaderRegistryDict] = None, algorithms: list[str] | None = None, verify_all_recipients: bool = True, strict_check_header: bool = True, ): self.header_registry: HeaderRegistryDict = {} self.header_registry.update(JWE_HEADER_REGISTRY) if header_registry is not None: self.header_registry.update(header_registry) self.allowed = algorithms self.verify_all_recipients = verify_all_recipients self.strict_check_header = strict_check_header @classmethod def register(cls, model: JWEAlgorithm) -> None: cls.algorithms[model.algorithm_location][model.name] = model # type: ignore if model.recommended: cls.recommended.append(model.name) def check_header(self, header: Header, check_more: bool = False) -> None: """Check and validate the fields in header part of a JWS object.""" check_crit_header(header) validate_registry_header(self.header_registry, header) alg = self.get_alg(header["alg"]) if alg.more_header_registry: validate_registry_header(alg.more_header_registry, header, check_more) if self.strict_check_header: allowed_registry = self.header_registry.copy() allowed_registry.update(alg.more_header_registry) check_supported_header(allowed_registry, header) elif self.strict_check_header: check_supported_header(self.header_registry, header) def get_alg(self, name: str) -> JWEAlgModel: """Get the allowed ("alg") algorithm instance of the given name. :param name: value of the ``alg``, e.g. ``ECDH-ES``, ``A128KW`` """ registry = self.algorithms["alg"] self._check_algorithm(name, registry) return registry[name] def get_enc(self, name: str) -> JWEEncModel: """Get the allowed ("enc") algorithm instance of the given name. :param name: value of the ``enc``, e.g. ``A128CBC-HS256``, ``A128GCM`` """ registry = self.algorithms["enc"] self._check_algorithm(name, registry) return registry[name] def get_zip(self, name: str) -> JWEZipModel: """Get the allowed ("zip") algorithm instance of the given name. :param name: value of the ``zip``, e.g. ``DEF`` """ registry = self.algorithms["zip"] self._check_algorithm(name, registry) return registry[name] def _check_algorithm(self, name: str, registry: dict[str, t.Any]) -> None: if name not in registry: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not supported") if self.allowed: if name not in self.allowed: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not allowed") else: if name not in self.recommended: raise UnsupportedAlgorithmError(f"Algorithm of '{name}' is not recommended") default_registry = JWERegistry() joserfc-1.1.0/src/joserfc/rfc7516/types.py000066400000000000000000000012441501424510600202000ustar00rootroot00000000000000import typing as t __all__ = [ "JSONRecipientDict", "FlattenedJSONSerialization", "GeneralJSONSerialization", ] class JSONRecipientDict(t.TypedDict, total=False): header: t.Dict[str, t.Any] encrypted_key: str class GeneralJSONSerialization(t.TypedDict, total=False): protected: str unprotected: t.Dict[str, t.Any] iv: str aad: str ciphertext: str tag: str recipients: t.List[JSONRecipientDict] class FlattenedJSONSerialization(t.TypedDict, total=False): protected: str unprotected: t.Dict[str, t.Any] iv: str aad: str ciphertext: str tag: str header: t.Dict[str, t.Any] encrypted_key: str joserfc-1.1.0/src/joserfc/rfc7517/000077500000000000000000000000001501424510600164625ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7517/__init__.py000066400000000000000000000000001501424510600205610ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7517/models.py000066400000000000000000000264331501424510600203270ustar00rootroot00000000000000from __future__ import annotations import typing as t from collections.abc import KeysView from abc import ABCMeta, abstractmethod from .types import DictKey, AnyKey, KeyParameters from ..registry import ( KeyParameterRegistryDict, JWK_PARAMETER_REGISTRY, KeyOperationRegistryDict, JWK_OPERATION_REGISTRY, ) from ..util import to_bytes from ..rfc7638 import thumbprint from ..errors import ( UnsupportedKeyUseError, UnsupportedKeyAlgorithmError, UnsupportedKeyOperationError, ) NativePrivateKey = t.TypeVar("NativePrivateKey") NativePublicKey = t.TypeVar("NativePublicKey") GenericKey = t.TypeVar("GenericKey", bound="BaseKey[t.Any, t.Any]") class NativeKeyBinding(metaclass=ABCMeta): use_key_ops_registry: t.ClassVar[t.Dict[str, t.List[str]]] = { "sig": ["sign", "verify"], "enc": ["encrypt", "decrypt", "wrapKey", "unwrapKey", "deriveKey", "deriveBits"], } @classmethod @abstractmethod def convert_raw_key_to_dict(cls, raw_key: t.Any, private: bool) -> DictKey: pass @classmethod @abstractmethod def import_from_dict(cls, value: DictKey) -> t.Any: pass @classmethod @abstractmethod def import_from_bytes(cls, value: bytes, password: t.Optional[t.Any] = None) -> t.Any: pass @staticmethod def as_bytes( key: GenericKey, encoding: t.Optional[t.Literal["PEM", "DER"]] = None, private: t.Optional[bool] = None, password: t.Optional[str] = None, ) -> bytes: raise NotImplementedError() @classmethod def validate_dict_key_registry(cls, dict_key: DictKey, registry: KeyParameterRegistryDict) -> None: for k in registry: if registry[k].required and k not in dict_key: raise ValueError(f"'{k}' is required") if k in dict_key: try: registry[k].validate(dict_key[k]) except ValueError as error: raise ValueError(f"'{k}' {error}") @classmethod def validate_dict_key_use_operations(cls, dict_key: DictKey) -> None: if "use" in dict_key and "key_ops" in dict_key: _use: str = dict_key["use"] # type: ignore operations = cls.use_key_ops_registry[_use] for op in dict_key["key_ops"]: if op not in operations: raise ValueError("'use' and 'key_ops' does not match") class BaseKey(t.Generic[NativePrivateKey, NativePublicKey], metaclass=ABCMeta): key_type: t.ClassVar[str] binding: t.ClassVar[t.Type[NativeKeyBinding]] value_registry: t.ClassVar[KeyParameterRegistryDict] param_registry: t.ClassVar[KeyParameterRegistryDict] = JWK_PARAMETER_REGISTRY operation_registry: t.ClassVar[KeyOperationRegistryDict] = JWK_OPERATION_REGISTRY thumbprint_digest_method: t.ClassVar[t.Literal["sha256", "sha384", "sha512"]] = "sha256" def __init__( self, raw_value: NativePrivateKey | NativePublicKey, original_value: t.Any, parameters: t.Optional[KeyParameters] = None, ): self._raw_value = raw_value self.original_value = original_value self.extra_parameters = parameters self._dict_value: DictKey = {} if isinstance(original_value, dict): if parameters is not None: data = {**original_value, **parameters, "kty": self.key_type} else: data = {**original_value, "kty": self.key_type} self.validate_dict_key(data) self._dict_value = data def __eq__(self, other: t.Any) -> bool: if not isinstance(other, self.__class__): return False return self.dict_value == other.dict_value def keys(self) -> KeysView[str]: return self.dict_value.keys() def __getitem__(self, k: str) -> str | list[str]: return self.dict_value[k] def get(self, k: str, default: str | list[str] | None = None) -> str | list[str] | None: return self.dict_value.get(k, default) def ensure_kid(self) -> None: """Ensure this key has a ``kid``. If ``kid`` is not provided by default, it will generate the kid with ``.thumbprint`` method, which is defined by RFC7638.""" if "kid" not in self.dict_value: self._dict_value["kid"] = self.thumbprint() @property def kid(self) -> str | None: """The "kid" value of the JSON Web Key.""" return t.cast(t.Optional[str], self.get("kid")) @property def alg(self) -> str | None: """The "alg" value of the JSON Web Key.""" return t.cast(t.Optional[str], self.get("alg")) @property def raw_value(self) -> t.Any: raise NotImplementedError() @property def is_private(self) -> bool: raise NotImplementedError() @property def dict_value(self) -> DictKey: """Property of the Key in Dict (JSON).""" if self._dict_value: return self._dict_value data = self.binding.convert_raw_key_to_dict(self.raw_value, self.is_private) if self.extra_parameters is not None: data.update(self.extra_parameters) # type: ignore data["kty"] = self.key_type self.validate_dict_key(data) self._dict_value = data return data @property def public_key(self) -> NativePublicKey: raise NotImplementedError() @property def private_key(self) -> t.Optional[NativePrivateKey]: raise NotImplementedError() def thumbprint(self) -> str: """Call this method will generate the thumbprint with algorithm defined in RFC7638.""" fields = [k for k in self.value_registry if self.value_registry[k].required] fields.append("kty") return thumbprint(self.dict_value, fields, self.thumbprint_digest_method) def as_dict(self, private: t.Optional[bool] = None, **params: t.Any) -> DictKey: """Output this key to a JWK format (in dict). By default, it will return the ``dict_value`` of this key. :param private: determine whether this method should output private key or not :param params: other parameters added into this key :raise: ValueError """ # check private conflicts if private and not self.is_private: raise ValueError("This key is not a private key.") data = self.dict_value.copy() if private is not False: data.update(params) return data # clear private fields for k in self.dict_value: if k in self.value_registry and self.value_registry[k].private: del data[k] data.update(params) return data def check_use(self, use: str) -> None: """Check if this key supports the given "use". :param use: this key is used for, e.g. "sig", "enc" :raise: UnsupportedKeyUseError """ designed_use = self.get("use") if designed_use and designed_use != use: raise UnsupportedKeyUseError(f"This key is designed to be used for '{designed_use}'") def check_alg(self, alg: str) -> None: """Check if this key supports the given "alg". :param alg: the algorithm this key is intended to be used, e.g. "HS256", "ECDH-EC" :raise: UnsupportedKeyAlgorithmError """ designed_alg = self.get("alg") if designed_alg and designed_alg != alg: raise UnsupportedKeyAlgorithmError(f"This key is designed for algorithm '{designed_alg}'") def check_key_op(self, operation: str) -> None: """Check if the given key_op is supported by this key. :param operation: key operation value, such as "sign", "encrypt". :raise: UnsupportedKeyOperationError """ key_ops = self.get("key_ops") if key_ops is not None and operation not in key_ops: raise UnsupportedKeyOperationError(f"Unsupported key_op '{operation}'") assert operation in self.operation_registry reg = self.operation_registry[operation] if reg.private and not self.is_private: raise UnsupportedKeyOperationError(f"Invalid key_op '{operation}' for public key") @t.overload def get_op_key(self, operation: t.Literal["verify", "encrypt", "wrapKey", "deriveKey"]) -> NativePublicKey: ... @t.overload def get_op_key(self, operation: t.Literal["sign", "decrypt", "unwrapKey"]) -> NativePrivateKey: ... def get_op_key(self, operation: str) -> NativePublicKey | NativePrivateKey: self.check_key_op(operation) reg = self.operation_registry[operation] if reg.private: assert self.private_key is not None return self.private_key return self.public_key @classmethod def validate_dict_key(cls, data: DictKey) -> None: cls.binding.validate_dict_key_registry(data, cls.param_registry) cls.binding.validate_dict_key_registry(data, cls.value_registry) cls.binding.validate_dict_key_use_operations(data) @classmethod def import_key( cls: t.Type[GenericKey], value: AnyKey, parameters: t.Optional[KeyParameters] = None, password: t.Optional[t.Any] = None, ) -> GenericKey: if isinstance(value, dict): cls.validate_dict_key(value) raw_key = cls.binding.import_from_dict(value) return cls(raw_key, value, parameters) raw_key = cls.binding.import_from_bytes(to_bytes(value), password) return cls(raw_key, value, parameters) @classmethod def generate_key( cls: t.Type[GenericKey], size_or_crv: t.Any, parameters: t.Optional[KeyParameters] = None, private: bool = True, auto_kid: bool = False, ) -> GenericKey: raise NotImplementedError() class SymmetricKey(BaseKey[bytes, bytes], metaclass=ABCMeta): @property def raw_value(self) -> bytes: """The raw key in bytes.""" return self._raw_value @property def is_private(self) -> bool: """A symmetric key will always be private.""" return True @property def public_key(self) -> bytes: """Returns the ``raw_value`` as the public key.""" return self.raw_value @property def private_key(self) -> bytes: """Returns the ``raw_value`` as the private key.""" return self.raw_value class AsymmetricKey(BaseKey[NativePrivateKey, NativePublicKey], metaclass=ABCMeta): @property def raw_value(self) -> t.Union[NativePublicKey, NativePrivateKey]: return self._raw_value def as_bytes( self, encoding: t.Optional[t.Literal["PEM", "DER"]] = None, private: t.Optional[bool] = None, password: t.Optional[str] = None, ) -> bytes: return self.binding.as_bytes(self, encoding, private, password) def as_pem(self, private: t.Optional[bool] = None, password: t.Optional[str] = None) -> bytes: return self.as_bytes(private=private, password=password) def as_der(self, private: t.Optional[bool] = None, password: t.Optional[str] = None) -> bytes: return self.as_bytes(encoding="DER", private=private, password=password) class CurveKey(AsymmetricKey[NativePrivateKey, NativePublicKey]): @property @abstractmethod def curve_name(self) -> str: pass @abstractmethod def exchange_derive_key(self, key: t.Any) -> bytes: pass joserfc-1.1.0/src/joserfc/rfc7517/pem.py000066400000000000000000000106521501424510600176210ustar00rootroot00000000000000from __future__ import annotations from typing import Any, Literal, cast from abc import ABCMeta, abstractmethod from cryptography.x509 import load_pem_x509_certificate from cryptography.hazmat.primitives.serialization import ( load_pem_private_key, load_pem_public_key, load_ssh_public_key, load_ssh_private_key, load_der_private_key, load_der_public_key, Encoding, PrivateFormat, PublicFormat, KeySerializationEncryption, BestAvailableEncryption, NoEncryption, ) from cryptography.hazmat.backends import default_backend from .models import NativeKeyBinding, GenericKey from .types import DictKey from ..util import to_bytes def load_pem_key(raw: bytes, ssh_type: bytes | None = None, password: bytes | None = None) -> Any: key: Any if ssh_type and raw.startswith(ssh_type): key = load_ssh_public_key(raw, backend=default_backend()) elif b"OPENSSH PRIVATE" in raw: key = load_ssh_private_key(raw, password=password, backend=default_backend()) elif b"PUBLIC" in raw: key = load_pem_public_key(raw, backend=default_backend()) elif b"PRIVATE" in raw: key = load_pem_private_key(raw, password=password, backend=default_backend()) elif b"CERTIFICATE" in raw: cert = load_pem_x509_certificate(raw, backend=default_backend()) return cert.public_key() else: try: key = load_der_private_key(raw, password=password, backend=default_backend()) except ValueError: key = load_der_public_key(raw, backend=default_backend()) return key def dump_pem_key( key: Any, encoding: Literal["PEM", "DER"] | None = None, private: bool | None = False, password: Any | None = None ) -> bytes: """Export key into PEM/DER format bytes. :param key: native cryptography key :param encoding: "PEM" or "DER" :param private: export private key or public key :param password: encrypt private key with password :return: bytes """ if encoding is None or encoding == "PEM": encoding_enum = Encoding.PEM elif encoding == "DER": encoding_enum = Encoding.DER else: # pragma: no cover raise ValueError("Invalid encoding: {!r}".format(encoding)) if private: encryption_algorithm: KeySerializationEncryption if password is None: encryption_algorithm = NoEncryption() else: encryption_algorithm = BestAvailableEncryption(to_bytes(password)) value = key.private_bytes( encoding=encoding_enum, format=PrivateFormat.PKCS8, encryption_algorithm=encryption_algorithm, ) else: value = key.public_bytes( encoding=encoding_enum, format=PublicFormat.SubjectPublicKeyInfo, ) return cast(bytes, value) class CryptographyBinding(NativeKeyBinding, metaclass=ABCMeta): ssh_type: bytes @classmethod def convert_raw_key_to_dict(cls, raw_key: Any, private: bool) -> DictKey: if private: value = cls.export_private_key(raw_key) else: value = cls.export_public_key(raw_key) return cast(DictKey, value) @classmethod def import_from_dict(cls, value: DictKey) -> Any: if "d" in value: return cls.import_private_key(value) return cls.import_public_key(value) @classmethod def import_from_bytes(cls, value: bytes, password: Any | None = None) -> Any: if password is not None: password = to_bytes(password) return load_pem_key(value, cls.ssh_type, password) @staticmethod def as_bytes( key: GenericKey, encoding: Literal["PEM", "DER"] | None = None, private: bool | None = False, password: Any | None = None, ) -> bytes: if private is True: return dump_pem_key(key.private_key, encoding, private, password) elif private is False: return dump_pem_key(key.public_key, encoding, private, password) return dump_pem_key(key.raw_value, encoding, key.is_private, password) @staticmethod @abstractmethod def import_private_key(value: Any) -> Any: pass @staticmethod @abstractmethod def import_public_key(value: Any) -> Any: pass @staticmethod @abstractmethod def export_private_key(value: Any) -> Any: pass @staticmethod @abstractmethod def export_public_key(value: Any) -> Any: pass joserfc-1.1.0/src/joserfc/rfc7517/types.py000066400000000000000000000010001501424510600201670ustar00rootroot00000000000000import typing as t __all__ = ["DictKey", "AnyKey", "KeyParameters"] #: JSON Web Key in dict DictKey = t.Dict[str, t.Union[str, t.List[str]]] #: Key in str, bytes and dict AnyKey = t.Union[str, bytes, DictKey] #: extra key parameters for JWK KeyParameters = t.TypedDict( "KeyParameters", { "use": str, "key_ops": t.List[str], "alg": str, "kid": str, "x5u": str, "x5c": t.List[str], "x5t": str, "x5t#S256": str, }, total=False, ) joserfc-1.1.0/src/joserfc/rfc7518/000077500000000000000000000000001501424510600164635ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7518/__init__.py000066400000000000000000000000001501424510600205620ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7518/derive_key.py000066400000000000000000000027701501424510600211710ustar00rootroot00000000000000from __future__ import annotations import struct from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash from ..registry import Header from ..util import to_bytes, urlsafe_b64decode __all__ = [ "derive_key_for_concat_kdf", "u32be_len_input", ] def derive_key_for_concat_kdf( shared_key: bytes, header: Header, cek_size: int, key_size: int | None, tag: bytes | None = None ) -> bytes: # PartyUInfo apu_info = u32be_len_input(header.get("apu"), True) # PartyVInfo apv_info = u32be_len_input(header.get("apv"), True) # SuppPubInfo if key_size: alg_id = u32be_len_input(header["alg"]) bit_size = key_size else: alg_id = u32be_len_input(header["enc"]) bit_size = cek_size pub_info = struct.pack(">I", bit_size) fixed_info = alg_id + apu_info + apv_info + pub_info if tag: cctag = u32be_len_input(tag) fixed_info += cctag ckdf = ConcatKDFHash( algorithm=hashes.SHA256(), length=bit_size // 8, otherinfo=fixed_info, backend=default_backend(), ) return ckdf.derive(shared_key) def u32be_len_input(s: bytes | str | None, use_base64: bool = False) -> bytes: if not s: return b"\x00\x00\x00\x00" sb: bytes if use_base64: sb = urlsafe_b64decode(to_bytes(s)) else: sb = to_bytes(s) return struct.pack(">I", len(sb)) + sb joserfc-1.1.0/src/joserfc/rfc7518/ec_key.py000066400000000000000000000132361501424510600203010ustar00rootroot00000000000000from __future__ import annotations import typing as t from functools import cached_property from cryptography.hazmat.primitives.asymmetric.ec import ( generate_private_key, ECDH, EllipticCurvePublicKey, EllipticCurvePrivateKey, EllipticCurvePrivateNumbers, EllipticCurvePublicNumbers, EllipticCurve, SECP256R1, SECP384R1, SECP521R1, ) from cryptography.hazmat.backends import default_backend from ..errors import InvalidExchangeKeyError from ..rfc7517.models import CurveKey from ..rfc7517.pem import CryptographyBinding from ..rfc7517.types import KeyParameters from ..util import base64_to_int, int_to_base64 from ..registry import KeyParameter __all__ = ["ECKey"] ECDictKey = t.TypedDict( "ECDictKey", { "crv": str, "x": str, "y": str, "d": str, # optional }, total=False, ) class ECBinding(CryptographyBinding): ssh_type = b"ecdsa-sha2-" _dss_curves: t.Dict[str, t.Type[EllipticCurve]] = {} _curves_dss: t.Dict[str, str] = {} @classmethod def register_curve(cls, name: str, curve: t.Type[EllipticCurve]) -> None: cls._dss_curves[name] = curve cls._curves_dss[str(curve.name)] = name @classmethod def generate_private_key(cls, name: str) -> EllipticCurvePrivateKey: if name not in cls._dss_curves: raise ValueError("Invalid crv value: '{}'".format(name)) curve = cls._dss_curves[name]() raw_key = generate_private_key( curve=curve, backend=default_backend(), ) return raw_key @classmethod def import_private_key(cls, obj: ECDictKey) -> EllipticCurvePrivateKey: curve = cls._dss_curves[obj["crv"]]() public_numbers = EllipticCurvePublicNumbers( base64_to_int(obj["x"]), base64_to_int(obj["y"]), curve, ) d = base64_to_int(obj["d"]) private_numbers = EllipticCurvePrivateNumbers(d, public_numbers) return private_numbers.private_key(default_backend()) @classmethod def export_private_key(cls, key: EllipticCurvePrivateKey) -> ECDictKey: numbers = key.private_numbers() return { "crv": cls._curves_dss[key.curve.name], "x": int_to_base64(numbers.public_numbers.x), "y": int_to_base64(numbers.public_numbers.y), "d": int_to_base64(numbers.private_value), } @classmethod def import_public_key(cls, obj: ECDictKey) -> EllipticCurvePublicKey: curve = cls._dss_curves[obj["crv"]]() public_numbers = EllipticCurvePublicNumbers( base64_to_int(obj["x"]), base64_to_int(obj["y"]), curve, ) return public_numbers.public_key(default_backend()) @classmethod def export_public_key(cls, key: EllipticCurvePublicKey) -> ECDictKey: numbers = key.public_numbers() return { "crv": cls._curves_dss[numbers.curve.name], "x": int_to_base64(numbers.x), "y": int_to_base64(numbers.y), } class ECKey(CurveKey[EllipticCurvePrivateKey, EllipticCurvePublicKey]): key_type = "EC" #: Registry definition for EC Key #: https://www.rfc-editor.org/rfc/rfc7518#section-6.2 value_registry = { "crv": KeyParameter("Curve", "str", private=False, required=True), "x": KeyParameter("X Coordinate", "str", private=False, required=True), "y": KeyParameter("Y Coordinate", "str", private=False, required=True), "d": KeyParameter("EC Private Key", "str", private=True, required=False), } binding = ECBinding @property def is_private(self) -> bool: return isinstance(self.raw_value, EllipticCurvePrivateKey) @cached_property def public_key(self) -> EllipticCurvePublicKey: if isinstance(self.raw_value, EllipticCurvePrivateKey): return self.raw_value.public_key() return self.raw_value @property def private_key(self) -> EllipticCurvePrivateKey | None: if isinstance(self.raw_value, EllipticCurvePrivateKey): return self.raw_value return None def exchange_derive_key(self, key: "ECKey") -> bytes: pubkey = key.get_op_key("deriveKey") if self.private_key and self.curve_name == key.curve_name: return self.private_key.exchange(ECDH(), pubkey) raise InvalidExchangeKeyError() @property def curve_name(self) -> str: return self.binding._curves_dss[self.raw_value.curve.name] @property def curve_key_size(self) -> int: return self.raw_value.curve.key_size @classmethod def generate_key( cls, crv: str | None = "P-256", parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> "ECKey": """Generate a ``ECKey`` with the given "crv" value. :param crv: ECKey curve name :param parameters: extra parameter in JWK :param private: generate a private key or public key :param auto_kid: add ``kid`` automatically """ if crv is None: crv = "P-256" raw_key = cls.binding.generate_private_key(crv) if private: key = cls(raw_key, raw_key, parameters) else: pub_key = raw_key.public_key() key = cls(pub_key, pub_key, parameters) if auto_kid: key.ensure_kid() return key # register default curves with their DSS (Digital Signature Standard) names # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf ECBinding.register_curve("P-256", SECP256R1) ECBinding.register_curve("P-384", SECP384R1) ECBinding.register_curve("P-521", SECP521R1) joserfc-1.1.0/src/joserfc/rfc7518/jwe_algs.py000066400000000000000000000304351501424510600206350ustar00rootroot00000000000000from __future__ import annotations import secrets from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.keywrap import ( aes_key_wrap, aes_key_unwrap, InvalidUnwrap, ) from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import GCM from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.exceptions import InvalidTag from .derive_key import derive_key_for_concat_kdf from .oct_key import OctKey from .rsa_key import RSAKey from .ec_key import ECKey from ..rfc7516.models import ( JWEAlgModel, JWEDirectEncryption, JWEKeyEncryption, JWEKeyWrapping, JWEKeyAgreement, JWEEncModel, Recipient, ) from ..util import to_bytes, urlsafe_b64encode, urlsafe_b64decode from ..registry import HeaderParameter from ..errors import ( InvalidKeyLengthError, DecodeError, ) class DirectAlgModel(JWEDirectEncryption): name = "dir" description = "Direct use of a shared symmetric key" recommended = True def compute_cek(self, size: int, recipient: Recipient[OctKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) cek = key.raw_value if len(cek) * 8 != size: raise InvalidKeyLengthError(f"A key of size {size} bits MUST be used") return cek class RSAAlgModel(JWEKeyEncryption): #: A key of size 2048 bits or larger MUST be used with these algorithms #: RSA1_5, RSA-OAEP, RSA-OAEP-256 key_size = 2048 key_types = ["RSA"] def __init__(self, name: str, description: str, pad_fn: padding.AsymmetricPadding, recommended: bool = False): self.name = name self.description = description self.padding = pad_fn self.recommended = recommended def encrypt_cek(self, cek: bytes, recipient: Recipient[RSAKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("encrypt") if op_key.key_size < self.key_size: raise InvalidKeyLengthError(f"A key of size {self.key_size} bits or larger MUST be used") return op_key.encrypt(cek, self.padding) def decrypt_cek(self, recipient: Recipient[RSAKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("decrypt") try: assert recipient.encrypted_key is not None cek = op_key.decrypt(recipient.encrypted_key, self.padding) except ValueError as error: raise DecodeError(str(error)) return cek class AESAlgModel(JWEKeyWrapping): def __init__(self, key_size: int, recommended: bool = False): self.name = f"A{key_size}KW" self.description = f"AES Key Wrap using {key_size}-bit key" self.key_size = key_size self.recommended = recommended def wrap_cek(self, cek: bytes, key: bytes) -> bytes: self.check_op_key(key) return aes_key_wrap(key, cek, default_backend()) def unwrap_cek(self, ek: bytes, key: bytes) -> bytes: self.check_op_key(key) try: cek = aes_key_unwrap(key, ek, default_backend()) except InvalidUnwrap: raise DecodeError("Unwrap AES key failed") return cek def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("wrapKey") return self.wrap_cek(cek, op_key) def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("unwrapKey") assert recipient.encrypted_key is not None return self.unwrap_cek(recipient.encrypted_key, op_key) class AESGCMAlgModel(JWEKeyWrapping): more_header_registry = { "iv": HeaderParameter("Initialization vector", "str", True), "tag": HeaderParameter("Authentication tag", "str", True), } def __init__(self, key_size: int): self.name = f"A{key_size}GCMKW" self.description = f"Key wrapping with AES GCM using {key_size}-bit key" self.key_size = key_size def wrap_cek(self, cek: bytes, key: bytes) -> bytes: # pragma: no cover raise RuntimeError(f"{self.name} can not be used together with Key Agreement") def unwrap_cek(self, ek: bytes, key: bytes) -> bytes: # pragma: no cover raise RuntimeError(f"{self.name} can not be used together with Key Agreement") def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("wrapKey") self.check_op_key(op_key) #: https://tools.ietf.org/html/rfc7518#section-4.7.1.1 #: The "iv" (initialization vector) Header Parameter value is the #: base64url-encoded representation of the 96-bit IV value iv_size = 96 iv = secrets.token_bytes(iv_size // 8) cipher = Cipher(AES(op_key), GCM(iv), backend=default_backend()) enc = cipher.encryptor() encrypted_key = enc.update(cek) + enc.finalize() recipient.add_header("iv", urlsafe_b64encode(iv).decode("ascii")) recipient.add_header("tag", urlsafe_b64encode(enc.tag).decode("ascii")) return encrypted_key def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes: key = recipient.recipient_key assert key is not None self.check_key_type(key) op_key = key.get_op_key("unwrapKey") self.check_op_key(op_key) headers = recipient.headers() assert "iv" in headers assert "tag" in headers iv = urlsafe_b64decode(to_bytes(headers["iv"])) tag = urlsafe_b64decode(to_bytes(headers["tag"])) cipher = Cipher(AES(op_key), GCM(iv, tag), backend=default_backend()) d = cipher.decryptor() try: assert recipient.encrypted_key is not None cek = d.update(recipient.encrypted_key) + d.finalize() except InvalidTag as error: raise DecodeError(str(error)) return cek class ECDHESAlgModel(JWEKeyAgreement): key_types = ["EC", "OKP"] more_header_registry = { "epk": HeaderParameter("Ephemeral Public Key", "jwk", True), "apu": HeaderParameter("Agreement PartyUInfo", "str"), "apv": HeaderParameter("Agreement PartyVInfo", "str"), } # https://tools.ietf.org/html/rfc7518#section-4.6 def __init__(self, key_wrapping: JWEKeyWrapping | None = None): if key_wrapping is None: self.name = "ECDH-ES" self.description = "ECDH-ES in the Direct Key Agreement mode" self.key_size = None self.recommended = True else: self.name = f"ECDH-ES+{key_wrapping.name}" self.description = f"ECDH-ES using Concat KDF and CEK wrapped with {key_wrapping.name}" self.key_size = key_wrapping.key_size self.recommended = key_wrapping.recommended self.key_wrapping = key_wrapping def encrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: recipient_key = recipient.recipient_key assert recipient_key is not None ephemeral_key = recipient.ephemeral_key assert ephemeral_key is not None shared_key = ephemeral_key.exchange_derive_key(recipient_key) headers = recipient.headers() return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size) def decrypt_agreed_upon_key(self, enc: JWEEncModel, recipient: Recipient[ECKey]) -> bytes: headers = recipient.headers() assert "epk" in headers recipient_key = recipient.recipient_key assert recipient_key is not None self.check_key_type(recipient_key) ephemeral_key = recipient_key.import_key(headers["epk"]) shared_key = recipient_key.exchange_derive_key(ephemeral_key) return derive_key_for_concat_kdf(shared_key, headers, enc.cek_size, self.key_size) class PBES2HSAlgModel(JWEKeyEncryption): # https://www.rfc-editor.org/rfc/rfc7518#section-4.8 key_size: int more_header_registry = { "p2s": HeaderParameter("PBES2 Salt Input", "str", True), "p2c": HeaderParameter("PBES2 Count", "int", True), } key_types = ["oct"] # A minimum iteration count of 1000 is RECOMMENDED. DEFAULT_P2C = 2048 def __init__(self, hash_size: int, key_wrapping: JWEKeyWrapping): self.name = f"PBES2-HS{hash_size}+{key_wrapping.name}" self.description = f"PBES2 with HMAC SHA-{hash_size} and {key_wrapping.name} wrapping" self.key_size = key_wrapping.key_size self.key_wrapping = key_wrapping self.hash_alg = getattr(hashes, f"SHA{hash_size}")() def compute_derived_key(self, key: bytes, p2s: bytes, p2c: int) -> bytes: # The salt value used is (UTF8(Alg) || 0x00 || Salt Input) salt = to_bytes(self.name) + b"\x00" + p2s kdf = PBKDF2HMAC( algorithm=self.hash_alg, length=self.key_size // 8, salt=salt, iterations=p2c, backend=default_backend(), ) return kdf.derive(key) def encrypt_cek(self, cek: bytes, recipient: Recipient[OctKey]) -> bytes: headers = recipient.headers() if "p2s" not in headers: p2s = secrets.token_bytes(16) recipient.add_header("p2s", urlsafe_b64encode(p2s).decode("ascii")) else: p2s = urlsafe_b64decode(to_bytes(headers["p2s"])) if "p2c" not in headers: # A minimum iteration count of 1000 is RECOMMENDED. p2c = self.DEFAULT_P2C recipient.add_header("p2c", p2c) else: p2c = headers["p2c"] key = recipient.recipient_key assert key is not None self.check_key_type(key) kek = self.compute_derived_key(key.get_op_key("deriveKey"), p2s, p2c) return self.key_wrapping.wrap_cek(cek, kek) def decrypt_cek(self, recipient: Recipient[OctKey]) -> bytes: headers = recipient.headers() assert "p2s" in headers assert "p2c" in headers p2s = urlsafe_b64decode(to_bytes(headers["p2s"])) p2c = headers["p2c"] key = recipient.recipient_key assert key is not None self.check_key_type(key) kek = self.compute_derived_key(key.get_op_key("deriveKey"), p2s, p2c) assert recipient.encrypted_key is not None return self.key_wrapping.unwrap_cek(recipient.encrypted_key, kek) A128KW = AESAlgModel(128, True) # A128KW, Recommended A192KW = AESAlgModel(192) # A192KW A256KW = AESAlgModel(256, True) # A256KW, Recommended #: https://www.rfc-editor.org/rfc/rfc7518#section-4.1 JWE_ALG_MODELS: list[JWEAlgModel] = [ # Avoid all RSA-PKCS1 v1.5 encryption algorithms ([RFC8017], Section 7.2), # preferring RSAES-OAEP ([RFC8017], Section 7.1). # https://www.rfc-editor.org/rfc/rfc8725#section-3.2 RSAAlgModel("RSA1_5", "RSAES-PKCS1-v1_5", padding.PKCS1v15()), RSAAlgModel( "RSA-OAEP", "RSAES OAEP using default parameters", padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None), True, ), # Recommended+ RSAAlgModel( "RSA-OAEP-256", "RSAES OAEP using SHA-256 and MGF1 with SHA-256", padding.OAEP(padding.MGF1(hashes.SHA256()), hashes.SHA256(), None), ), A128KW, A192KW, A256KW, DirectAlgModel(), # dir, Recommended ECDHESAlgModel(None), # ECDH-ES, Recommended+ ECDHESAlgModel(A128KW), # ECDH-ES+A128KW, Recommended ECDHESAlgModel(A192KW), # ECDH-ES+A192KW ECDHESAlgModel(A256KW), # ECDH-ES+A256KW, Recommended AESGCMAlgModel(128), # A128GCMKW AESGCMAlgModel(192), # A192GCMKW AESGCMAlgModel(256), # A256GCMKW PBES2HSAlgModel(256, A128KW), # PBES2-HS256+A128KW PBES2HSAlgModel(384, A192KW), # PBES2-HS384+A192KW PBES2HSAlgModel(512, A256KW), # PBES2-HS512+A256KW ] joserfc-1.1.0/src/joserfc/rfc7518/jwe_encs.py000066400000000000000000000103331501424510600206320ustar00rootroot00000000000000""" authlib.jose.rfc7518 ~~~~~~~~~~~~~~~~~~~~ Cryptographic Models for Cryptographic Models for Content Encryption per `Section 5`_. .. _`Section 5`: https://tools.ietf.org/html/rfc7518#section-5 """ from __future__ import annotations import hmac import hashlib from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher from cryptography.hazmat.primitives.ciphers.algorithms import AES from cryptography.hazmat.primitives.ciphers.modes import GCM, CBC from cryptography.hazmat.primitives.padding import PKCS7 from cryptography.exceptions import InvalidTag from ..rfc7516.models import JWEEncModel from ..errors import DecodeError from .util import encode_int class CBCHS2EncModel(JWEEncModel): # The IV used is a 128-bit value generated randomly or # pseudo-randomly for use in the cipher. iv_size = 128 recommended = True def __init__(self, key_size: int, hash_type: int): self.name = f"A{key_size}CBC-HS{hash_type}" self.description = f"AES_{key_size}_CBC_HMAC_SHA_{hash_type} authenticated encryption algorithm" # key size in bit self.key_size = key_size # key size in byte self.key_len = key_size // 8 self.cek_size = key_size * 2 self.hash_alg = getattr(hashlib, f"sha{hash_type}") def _hmac(self, ciphertext: bytes, aad: bytes, iv: bytes, key: bytes) -> bytes: al = encode_int(len(aad) * 8, 64) msg = aad + iv + ciphertext + al d = hmac.new(key, msg, self.hash_alg).digest() return d[: self.key_len] def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: """Key Encryption with AES_CBC_HMAC_SHA2.""" hkey = cek[: self.key_len] ekey = cek[self.key_len :] pad = PKCS7(AES.block_size).padder() padded_data = pad.update(plaintext) + pad.finalize() cipher = Cipher(AES(ekey), CBC(iv), backend=default_backend()) enc = cipher.encryptor() ciphertext = enc.update(padded_data) + enc.finalize() tag = self._hmac(ciphertext, aad, iv, hkey) return ciphertext, tag def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: """Key Decryption with AES AES_CBC_HMAC_SHA2.""" hkey = cek[: self.key_len] dkey = cek[self.key_len :] ctag = self._hmac(ciphertext, aad, iv, hkey) if not hmac.compare_digest(ctag, tag): raise DecodeError("tag does not match") cipher = Cipher(AES(dkey), CBC(iv), backend=default_backend()) d = cipher.decryptor() data = d.update(ciphertext) + d.finalize() unpad = PKCS7(AES.block_size).unpadder() return unpad.update(data) + unpad.finalize() class GCMEncModel(JWEEncModel): # Use of an IV of size 96 bits is REQUIRED with this algorithm. # https://tools.ietf.org/html/rfc7518#section-5.3 iv_size = 96 recommended = True def __init__(self, key_size: int): self.name = f"A{key_size}GCM" self.description = f"AES GCM using {key_size}-bit key" self.key_size = key_size self.cek_size = key_size def encrypt(self, plaintext: bytes, cek: bytes, iv: bytes, aad: bytes) -> tuple[bytes, bytes]: """Key Encryption with AES GCM""" cipher = Cipher(AES(cek), GCM(iv), backend=default_backend()) enc = cipher.encryptor() enc.authenticate_additional_data(aad) ciphertext = enc.update(plaintext) + enc.finalize() return ciphertext, enc.tag def decrypt(self, ciphertext: bytes, tag: bytes, cek: bytes, iv: bytes, aad: bytes) -> bytes: """Key Decryption with AES GCM""" cipher = Cipher(AES(cek), GCM(iv, tag), backend=default_backend()) d = cipher.decryptor() d.authenticate_additional_data(aad) try: return d.update(ciphertext) + d.finalize() except InvalidTag as error: raise DecodeError(str(error)) JWE_ENC_MODELS: list[JWEEncModel] = [ CBCHS2EncModel(128, 256), # A128CBC-HS256 CBCHS2EncModel(192, 384), # A192CBC-HS384 CBCHS2EncModel(256, 512), # A256CBC-HS512 GCMEncModel(128), # A128GCM GCMEncModel(192), # A192GCM GCMEncModel(256), # A256GCM ] joserfc-1.1.0/src/joserfc/rfc7518/jwe_zips.py000066400000000000000000000020431501424510600206660ustar00rootroot00000000000000from __future__ import annotations import zlib from ..rfc7516.models import JWEZipModel from ..errors import ExceededSizeError GZIP_HEAD = bytes([120, 156]) MAX_SIZE = 250 * 1024 class DeflateZipModel(JWEZipModel): name = "DEF" description = "DEFLATE" def compress(self, s: bytes) -> bytes: """Compress bytes data with DEFLATE algorithm.""" data = zlib.compress(s) # https://datatracker.ietf.org/doc/html/rfc1951 # since DEF is always gzip, we can drop gzip headers and tail return data[2:-4] def decompress(self, s: bytes) -> bytes: """Decompress DEFLATE bytes data.""" if s.startswith(GZIP_HEAD): decompressor = zlib.decompressobj() else: decompressor = zlib.decompressobj(-zlib.MAX_WBITS) value = decompressor.decompress(s, MAX_SIZE) if decompressor.unconsumed_tail: raise ExceededSizeError(f"Decompressed string exceeds {MAX_SIZE} bytes") return value JWE_ZIP_MODELS: list[JWEZipModel] = [DeflateZipModel()] joserfc-1.1.0/src/joserfc/rfc7518/jws_algs.py000066400000000000000000000147011501424510600206510ustar00rootroot00000000000000""" joserfc.rfc7518.jws_algs ~~~~~~~~~~~~~~~~~~~~~~~~ Originally designed in ``authlib.jose.rfc7518``. "alg" (Algorithm) Header Parameter Values for JWS per `Section 3`_. .. _`Section 3`: https://tools.ietf.org/html/rfc7518#section-3 """ import hmac import hashlib import typing as t from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, encode_dss_signature, ) from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric import padding from cryptography.exceptions import InvalidSignature from ..rfc7515.model import JWSAlgModel from .oct_key import OctKey from .rsa_key import RSAKey from .ec_key import ECKey from .util import encode_int, decode_int class NoneAlgModel(JWSAlgModel): name = "none" description = "No digital signature or MAC performed" def sign(self, msg: bytes, key: t.Any) -> bytes: return b"" def verify(self, msg: bytes, sig: bytes, key: t.Any) -> bool: return sig == b"" class HMACAlgModel(JWSAlgModel): """HMAC using SHA algorithms for JWS. Available algorithms: - HS256: HMAC using SHA-256 - HS384: HMAC using SHA-384 - HS512: HMAC using SHA-512 """ SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.name = f"HS{sha_type}" self.description = f"HMAC using SHA-{sha_type}" self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") def sign(self, msg: bytes, key: OctKey) -> bytes: # it is faster than the one in cryptography op_key = key.get_op_key("sign") return hmac.new(op_key, msg, self.hash_alg).digest() def verify(self, msg: bytes, sig: bytes, key: OctKey) -> bool: op_key = key.get_op_key("verify") v_sig = hmac.new(op_key, msg, self.hash_alg).digest() return hmac.compare_digest(sig, v_sig) class RSAAlgModel(JWSAlgModel): """RSA using SHA algorithms for JWS. Available algorithms: - RS256: RSASSA-PKCS1-v1_5 using SHA-256 - RS384: RSASSA-PKCS1-v1_5 using SHA-384 - RS512: RSASSA-PKCS1-v1_5 using SHA-512 """ key_type = "RSA" SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 padding = padding.PKCS1v15() def __init__(self, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.name = f"RS{sha_type}" self.description = f"RSASSA-PKCS1-v1_5 using SHA-{sha_type}" self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") def sign(self, msg: bytes, key: RSAKey) -> bytes: op_key = key.get_op_key("sign") return op_key.sign(msg, self.padding, self.hash_alg()) def verify(self, msg: bytes, sig: bytes, key: RSAKey) -> bool: op_key = key.get_op_key("verify") try: op_key.verify(sig, msg, self.padding, self.hash_alg()) return True except InvalidSignature: return False class ECAlgModel(JWSAlgModel): """ECDSA using SHA algorithms for JWS. Available algorithms: - ES256: ECDSA using P-256 and SHA-256 - ES384: ECDSA using P-384 and SHA-384 - ES512: ECDSA using P-521 and SHA-512 """ key_type = "EC" SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, name: str, curve: str, sha_type: t.Literal[256, 384, 512], recommended: bool = False): self.name = name self.curve = curve self.description = f"ECDSA using {self.curve} and SHA-{sha_type}" self.recommended = recommended self.hash_alg = getattr(self, f"SHA{sha_type}") def _check_key(self, key: ECKey) -> ECKey: if key.curve_name != self.curve: raise ValueError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed") return key def sign(self, msg: bytes, key: ECKey) -> bytes: self._check_key(key) op_key = key.get_op_key("sign") der_sig = op_key.sign(msg, ECDSA(self.hash_alg())) r, s = decode_dss_signature(der_sig) size = key.curve_key_size return encode_int(r, size) + encode_int(s, size) def verify(self, msg: bytes, sig: bytes, key: ECKey) -> bool: self._check_key(key) key_size = key.curve_key_size length = (key_size + 7) // 8 if len(sig) != 2 * length: return False r = decode_int(sig[:length]) s = decode_int(sig[length:]) der_sig = encode_dss_signature(r, s) try: op_key = key.get_op_key("verify") op_key.verify(der_sig, msg, ECDSA(self.hash_alg())) return True except InvalidSignature: return False class RSAPSSAlgModel(JWSAlgModel): """RSASSA-PSS using SHA algorithms for JWS. Available algorithms: - PS256: RSASSA-PSS using SHA-256 and MGF1 with SHA-256 - PS384: RSASSA-PSS using SHA-384 and MGF1 with SHA-384 - PS512: RSASSA-PSS using SHA-512 and MGF1 with SHA-512 """ key_type = "RSA" SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, sha_type: t.Literal[256, 384, 512]): self.name = f"PS{sha_type}" self.description = f"RSASSA-PSS using SHA-{sha_type} and MGF1 with SHA-{sha_type}" self.hash_alg = getattr(self, f"SHA{sha_type}") self.padding = padding.PSS(mgf=padding.MGF1(self.hash_alg()), salt_length=self.hash_alg.digest_size) def sign(self, msg: bytes, key: RSAKey) -> bytes: op_key = key.get_op_key("sign") return op_key.sign(msg, self.padding, self.hash_alg()) def verify(self, msg: bytes, sig: bytes, key: RSAKey) -> bool: op_key = key.get_op_key("verify") try: op_key.verify(sig, msg, self.padding, self.hash_alg()) return True except InvalidSignature: return False JWS_ALGORITHMS: t.List[JWSAlgModel] = [ NoneAlgModel(), # none HMACAlgModel(256, True), # HS256 HMACAlgModel(384), # HS384 HMACAlgModel(512), # HS512 RSAAlgModel(256, True), # RS256 RSAAlgModel(384), # RS384 RSAAlgModel(512), # RS512 ECAlgModel("ES256", "P-256", 256, True), ECAlgModel("ES384", "P-384", 384), ECAlgModel("ES512", "P-521", 512), RSAPSSAlgModel(256), # PS256 RSAPSSAlgModel(384), # PS384 RSAPSSAlgModel(512), # PS512 ] joserfc-1.1.0/src/joserfc/rfc7518/oct_key.py000066400000000000000000000043031501424510600204720ustar00rootroot00000000000000from __future__ import annotations from typing import Any import secrets import warnings from ..util import ( to_bytes, urlsafe_b64decode, urlsafe_b64encode, ) from ..registry import KeyParameter from ..rfc7517.models import SymmetricKey, NativeKeyBinding from ..rfc7517.types import KeyParameters, DictKey POSSIBLE_UNSAFE_KEYS = ( b"-----BEGIN ", b"---- BEGIN ", b"ssh-rsa ", b"ssh-dss ", b"ssh-ed25519 ", b"ecdsa-sha2-", ) class OctBinding(NativeKeyBinding): @classmethod def convert_raw_key_to_dict(cls, value: bytes, private: bool) -> DictKey: k = urlsafe_b64encode(value).decode("utf-8") return {"k": k} @classmethod def import_from_dict(cls, value: DictKey) -> bytes: return urlsafe_b64decode(to_bytes(value["k"])) @classmethod def import_from_bytes(cls, value: bytes, password: Any | None = None) -> bytes: # security check if value.startswith(POSSIBLE_UNSAFE_KEYS): warnings.warn("This key may not be safe to import") return value class OctKey(SymmetricKey): """OctKey is a symmetric key, defined by RFC7518 Section 6.4.""" key_type = "oct" binding = OctBinding #: https://www.rfc-editor.org/rfc/rfc7518#section-6.4 value_registry = {"k": KeyParameter("Key Value", "str", True, True)} @classmethod def generate_key( cls, key_size: int | None = 256, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> "OctKey": """Generate a ``OctKey`` with the given bit size (not bytes). :param key_size: size in bit :param parameters: extra parameter in JWK :param private: must be True :param auto_kid: add ``kid`` automatically """ if not private: raise ValueError("oct key can not be generated as public") if key_size is None: key_size = 256 if key_size % 8 != 0: raise ValueError("Invalid bit size for oct key") raw_key = secrets.token_bytes(key_size // 8) key: OctKey = cls(raw_key, raw_key, parameters) if auto_kid: key.ensure_kid() return key joserfc-1.1.0/src/joserfc/rfc7518/rsa_key.py000066400000000000000000000136631501424510600205030ustar00rootroot00000000000000from __future__ import annotations from typing import TypedDict from functools import cached_property from cryptography.hazmat.primitives.asymmetric.rsa import ( generate_private_key, RSAPublicKey, RSAPrivateKey, RSAPrivateNumbers, RSAPublicNumbers, rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp, ) from cryptography.hazmat.backends import default_backend from ..registry import KeyParameter from ..rfc7517.models import AsymmetricKey from ..rfc7517.pem import CryptographyBinding from ..rfc7517.types import KeyParameters from ..util import int_to_base64, base64_to_int RSADictKey = TypedDict( "RSADictKey", { "n": str, "e": str, "d": str, "p": str, "q": str, "dp": str, "dq": str, "qi": str, }, total=False, ) class RSABinding(CryptographyBinding): ssh_type = b"ssh-rsa" @staticmethod def import_private_key(obj: RSADictKey) -> RSAPrivateKey: if "oth" in obj: # pragma: no cover # https://tools.ietf.org/html/rfc7518#section-6.3.2.7 raise ValueError('"oth" is not supported yet') public_numbers = RSAPublicNumbers(base64_to_int(obj["e"]), base64_to_int(obj["n"])) if has_all_prime_factors(obj): numbers = RSAPrivateNumbers( d=base64_to_int(obj["d"]), p=base64_to_int(obj["p"]), q=base64_to_int(obj["q"]), dmp1=base64_to_int(obj["dp"]), dmq1=base64_to_int(obj["dq"]), iqmp=base64_to_int(obj["qi"]), public_numbers=public_numbers, ) else: d = base64_to_int(obj["d"]) p, q = rsa_recover_prime_factors(public_numbers.n, d, public_numbers.e) numbers = RSAPrivateNumbers( d=d, p=p, q=q, dmp1=rsa_crt_dmp1(d, p), dmq1=rsa_crt_dmq1(d, q), iqmp=rsa_crt_iqmp(p, q), public_numbers=public_numbers, ) return numbers.private_key(default_backend()) @staticmethod def export_private_key(key: RSAPrivateKey) -> RSADictKey: numbers = key.private_numbers() return { "n": int_to_base64(numbers.public_numbers.n), "e": int_to_base64(numbers.public_numbers.e), "d": int_to_base64(numbers.d), "p": int_to_base64(numbers.p), "q": int_to_base64(numbers.q), "dp": int_to_base64(numbers.dmp1), "dq": int_to_base64(numbers.dmq1), "qi": int_to_base64(numbers.iqmp), } @staticmethod def import_public_key(obj: RSADictKey) -> RSAPublicKey: numbers = RSAPublicNumbers(base64_to_int(obj["e"]), base64_to_int(obj["n"])) return numbers.public_key(default_backend()) @staticmethod def export_public_key(key: RSAPublicKey) -> dict[str, str]: numbers = key.public_numbers() return {"n": int_to_base64(numbers.n), "e": int_to_base64(numbers.e)} class RSAKey(AsymmetricKey[RSAPrivateKey, RSAPublicKey]): key_type = "RSA" #: Registry definition for RSA Key #: https://www.rfc-editor.org/rfc/rfc7518#section-6.3 value_registry = { "n": KeyParameter("Modulus", "str", private=False, required=True), "e": KeyParameter("Exponent", "str", private=False, required=True), "d": KeyParameter("Private Exponent", "str", private=True, required=False), "p": KeyParameter("First Prime Factor", "str", private=True, required=False), "q": KeyParameter("Second Prime Factor", "str", private=True, required=False), "dp": KeyParameter("First Factor CRT Exponent", "str", private=True, required=False), "dq": KeyParameter("Second Factor CRT Exponent", "str", private=True, required=False), "qi": KeyParameter("First CRT Coefficient", "str", private=True, required=False), "oth": KeyParameter("Other Primes Info", "none", private=True, required=False), } binding = RSABinding @property def is_private(self) -> bool: return isinstance(self.raw_value, RSAPrivateKey) @cached_property def public_key(self) -> RSAPublicKey: if isinstance(self.raw_value, RSAPrivateKey): return self.raw_value.public_key() return self.raw_value @property def private_key(self) -> RSAPrivateKey | None: if isinstance(self.raw_value, RSAPrivateKey): return self.raw_value return None @classmethod def generate_key( cls, key_size: int | None = 2048, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> "RSAKey": """Generate a ``RSAKey`` with the given bit size (not bytes). :param key_size: size in bit :param parameters: extra parameter in JWK :param private: generate a private key or public key :param auto_kid: add ``kid`` automatically """ if key_size is None: key_size = 2048 if key_size < 512: raise ValueError("key_size must not be less than 512") if key_size % 8 != 0: raise ValueError("Invalid key_size for RSAKey") raw_key = generate_private_key( public_exponent=65537, key_size=key_size, backend=default_backend(), ) if private: key = cls(raw_key, raw_key, parameters) else: pub_key = raw_key.public_key() key = cls(pub_key, pub_key, parameters) if auto_kid: key.ensure_kid() return key def has_all_prime_factors(obj: RSADictKey) -> bool: props = ["p", "q", "dp", "dq", "qi"] props_found = [prop in obj for prop in props] if all(props_found): return True if any(props_found): raise ValueError("RSA key must include all parameters if any are present besides d") return False joserfc-1.1.0/src/joserfc/rfc7518/util.py000066400000000000000000000004521501424510600200130ustar00rootroot00000000000000import binascii def encode_int(num: int, bits: int) -> bytes: length = ((bits + 7) // 8) * 2 padded_hex = "%0*x" % (length, num) big_endian = binascii.a2b_hex(padded_hex.encode("ascii")) return big_endian def decode_int(s: bytes) -> int: return int(binascii.b2a_hex(s), 16) joserfc-1.1.0/src/joserfc/rfc7519/000077500000000000000000000000001501424510600164645ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7519/__init__.py000066400000000000000000000000001501424510600205630ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7519/claims.py000066400000000000000000000031111501424510600203020ustar00rootroot00000000000000from __future__ import annotations import re import json import datetime import calendar from json import JSONEncoder from typing import Dict, Any, Type from ..util import to_bytes from ..errors import InsecureClaimError SENSITIVE_NAMES = ("password", "token", "secret", "secret_key", "api_key") SENSITIVE_VALUES = re.compile( r"|".join( [ # http://www.richardsramblings.com/regex/credit-card-numbers/ r"\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b", # various private keys r"-----BEGIN[A-Z ]+PRIVATE KEY-----.+-----END[A-Z ]+PRIVATE KEY-----", # social security numbers (US) r"^\b(?!(000|666|9))\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b", ] ), re.DOTALL, ) Claims = Dict[str, Any] def convert_claims(claims: Claims, encoder_cls: Type[JSONEncoder] | None = None) -> bytes: """Turn claims into bytes payload.""" for k in ["exp", "iat", "nbf"]: claim = claims.get(k) if isinstance(claim, datetime.datetime): claims[k] = calendar.timegm(claim.utctimetuple()) content = json.dumps(claims, ensure_ascii=False, separators=(",", ":"), cls=encoder_cls) return to_bytes(content) def check_sensitive_data(claims: Claims) -> None: """Check if claims contains sensitive information.""" for k in claims: # check claims key name if k in SENSITIVE_NAMES: raise InsecureClaimError(k) # check claims values v = claims[k] if isinstance(v, str) and SENSITIVE_VALUES.search(v): raise InsecureClaimError(k) joserfc-1.1.0/src/joserfc/rfc7519/registry.py000066400000000000000000000131471501424510600207140ustar00rootroot00000000000000from __future__ import annotations import time from typing import TypedDict, Any from ..errors import ( MissingClaimError, InvalidClaimError, ExpiredTokenError, InvalidTokenError, ) #: http://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests class ClaimsOption(TypedDict, total=False): essential: bool allow_blank: bool | None value: str | int | bool values: list[str | int | bool] | list[str] | list[int] | list[bool] class ClaimsRegistry: """Requesting "claims" for JWT with the given conditions.""" def __init__(self, **kwargs: ClaimsOption): self.options = kwargs self.essential_keys = {key for key in kwargs if kwargs[key].get("essential")} def check_value(self, claim_name: str, value: Any) -> None: option = self.options.get(claim_name) if option: allow_blank = option.get("allow_blank") if not allow_blank and value == "": raise InvalidClaimError(claim_name) option_value = option.get("value") if option_value is not None and value != option_value: raise InvalidClaimError(claim_name) option_values = option.get("values") if option_values is not None and value not in option_values: raise InvalidClaimError(claim_name) def validate(self, claims: dict[str, Any]) -> None: missed_keys = {key for key in self.essential_keys if claims.get(key) is None} if missed_keys: raise MissingClaimError(",".join(sorted(missed_keys))) for key in claims: value = claims[key] func = getattr(self, "validate_" + key, None) if func: func(value) elif key in self.options: self.check_value(key, value) class JWTClaimsRegistry(ClaimsRegistry): def __init__(self, now: int | None = None, leeway: int = 0, **kwargs: ClaimsOption): if now is None: now = int(time.time()) self.now = now self.leeway = leeway super().__init__(**kwargs) def validate_aud(self, value: str | list[str]) -> None: """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. """ option = self.options.get("aud") if not option: return option_values = option.get("values") if option_values is None: option_value = option.get("value") if option_value: option_values = [option_value] if not option_values: return if isinstance(value, list): aud_list = value else: aud_list = [value] if not any([v in aud_list for v in option_values]): raise InvalidClaimError("aud") def validate_exp(self, value: int) -> None: """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. """ if not _validate_numeric_time(value): raise InvalidClaimError("exp") if value < (self.now - self.leeway): raise ExpiredTokenError() self.check_value("exp", value) def validate_nbf(self, value: int) -> None: """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. """ if not _validate_numeric_time(value): raise InvalidClaimError("nbf") if value > (self.now + self.leeway): raise InvalidTokenError() self.check_value("nbf", value) def validate_iat(self, value: int) -> None: """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. """ if not _validate_numeric_time(value): raise InvalidClaimError("iat") if value > (self.now + self.leeway): raise InvalidTokenError() self.check_value("iat", value) def _validate_numeric_time(s: int) -> bool: return isinstance(s, (int, float)) joserfc-1.1.0/src/joserfc/rfc7638/000077500000000000000000000000001501424510600164665ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7638/__init__.py000066400000000000000000000012071501424510600205770ustar00rootroot00000000000000import typing as t import json import hashlib from collections import OrderedDict from ..util import to_bytes, urlsafe_b64encode def thumbprint( dict_value: t.Dict[str, t.Any], fields: t.List[str], digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256", ) -> str: sorted_fields = sorted(fields) data = OrderedDict() for k in sorted_fields: data[k] = dict_value[k] json_data = json.dumps(data, ensure_ascii=True, separators=(",", ":")) hash_value = hashlib.new(digest_method, to_bytes(json_data)) digest_data = hash_value.digest() return urlsafe_b64encode(digest_data).decode("utf-8") joserfc-1.1.0/src/joserfc/rfc7797/000077500000000000000000000000001501424510600164745ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc7797/__init__.py000066400000000000000000000004251501424510600206060ustar00rootroot00000000000000from .registry import JWSRegistry from .compact import serialize_compact, deserialize_compact from .json import serialize_json, deserialize_json __all__ = [ "JWSRegistry", "serialize_compact", "deserialize_compact", "serialize_json", "deserialize_json", ] joserfc-1.1.0/src/joserfc/rfc7797/compact.py000066400000000000000000000072561501424510600205060ustar00rootroot00000000000000from __future__ import annotations import typing as t import re from ..registry import Header from ..jwk import KeyFlexible, guess_key from ..jws import ( CompactSignature, JWSRegistry as _JWSRegistry, serialize_compact as _serialize_compact, deserialize_compact as _deserialize_compact, ) from ..util import ( to_bytes, to_str, json_b64encode, urlsafe_b64encode, urlsafe_b64decode, ) from ..rfc7515.compact import decode_header from ..errors import BadSignatureError from .registry import JWSRegistry def serialize_compact( protected: Header, payload: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: t.Optional[_JWSRegistry] = None, ) -> str: if "b64" not in protected: return _serialize_compact(protected, payload, private_key, algorithms, registry) if registry is None: registry = JWSRegistry(algorithms=algorithms) if protected["b64"] is True: return _serialize_compact(protected, payload, private_key, registry=registry) registry.check_header(protected) obj = CompactSignature(protected, to_bytes(payload)) alg = registry.get_alg(protected["alg"]) key = guess_key(private_key, obj, True) key.check_use("sig") header_segment = json_b64encode(protected) signing_input = header_segment + b"." + obj.payload signature = urlsafe_b64encode(alg.sign(signing_input, key)) # if need to detach payload if __is_urlsafe_characters(payload): out = signing_input + b"." + signature else: out = header_segment + b".." + signature return out.decode("utf-8") def deserialize_compact( value: bytes | str, public_key: KeyFlexible, payload: t.Optional[bytes | str] = None, algorithms: list[str] | None = None, registry: t.Optional[JWSRegistry] = None, ) -> CompactSignature: obj = _extract_compact(to_bytes(value), payload) if obj is None: return _deserialize_compact(value, public_key, algorithms, registry) if registry is None: registry = JWSRegistry(algorithms=algorithms) if obj is True: return _deserialize_compact(value, public_key, registry=registry) headers = obj.headers() registry.check_header(headers) key = guess_key(public_key, obj) key.check_use("sig") alg = registry.get_alg(headers["alg"]) signing_input = obj.segments["header"] + b"." + obj.payload sig = urlsafe_b64decode(obj.segments["signature"]) if not alg.verify(signing_input, sig, key): raise BadSignatureError() assert isinstance(obj, CompactSignature) return obj # https://datatracker.ietf.org/doc/html/rfc7797#section-5.2 # the application MUST ensure that the payload contains only the URL-safe # characters 'a'-'z', 'A'-'Z', '0'-'9', dash ('-'), underscore ('_'), # and tilde ('~') _re_urlsafe = re.compile("^[a-zA-Z0-9-_~]+$") def __is_urlsafe_characters(s: bytes | str) -> bool: return bool(_re_urlsafe.match(to_str(s))) def _extract_compact(value: bytes, payload: t.Optional[bytes | str] = None) -> t.Any: parts = value.split(b".") if len(parts) != 3: raise ValueError("Invalid JSON Web Signature") header_segment, payload_segment, signature_segment = parts protected = decode_header(header_segment) if "b64" not in protected: return None if protected["b64"] is True: return True if payload: obj = CompactSignature(protected, to_bytes(payload)) else: obj = CompactSignature(protected, payload_segment) obj.segments.update( { "header": header_segment, "payload": payload_segment, "signature": signature_segment, } ) return obj joserfc-1.1.0/src/joserfc/rfc7797/json.py000066400000000000000000000070221501424510600200200ustar00rootroot00000000000000from __future__ import annotations import typing as t from ..rfc7515.json import verify_signature from ..rfc7515.types import JSONSignatureDict from ..jws import ( HeaderDict, HeaderMember, FlattenedJSONSignature, FlattenedJSONSerialization, JWSRegistry as _JWSRegistry, serialize_json as _serialize_json, deserialize_json as _deserialize_json, ) from ..jwk import Key, KeyFlexible, guess_key from ..util import ( to_bytes, to_str, json_b64encode, json_b64decode, urlsafe_b64encode, ) from ..errors import BadSignatureError from .registry import JWSRegistry def serialize_json( member: HeaderDict, payload: bytes | str, private_key: KeyFlexible, algorithms: list[str] | None = None, registry: t.Optional[_JWSRegistry] = None, ) -> FlattenedJSONSerialization: _member = HeaderMember(**member) headers = _member.headers() if "b64" not in headers: return _serialize_json(member, payload, private_key, algorithms, registry) if registry is None: registry = JWSRegistry(algorithms=algorithms) if headers["b64"] is True: return _serialize_json(member, payload, private_key, registry=registry) registry.check_header(headers) key = guess_key(private_key, _member, True) key.check_use("sig") alg = registry.get_alg(headers["alg"]) if _member.protected: protected_segment = json_b64encode(_member.protected) else: protected_segment = b"" signing_input = b".".join([protected_segment, to_bytes(payload)]) signature = urlsafe_b64encode(alg.sign(signing_input, key)) rv: FlattenedJSONSerialization = { "payload": to_str(payload), "signature": to_str(signature), } if protected_segment: rv["protected"] = to_str(protected_segment) if _member.header: rv["header"] = _member.header return rv def deserialize_json( value: FlattenedJSONSerialization, public_key: KeyFlexible, algorithms: list[str] | None = None, registry: t.Optional[_JWSRegistry] = None, ) -> FlattenedJSONSignature: obj = _extract_json(value) if obj is None: return _deserialize_json(value, public_key, algorithms, registry) if registry is None: registry = JWSRegistry(algorithms=algorithms) headers = obj.headers() if headers["b64"] is True: return _deserialize_json(value, public_key, registry=registry) payload_segment = obj.segments["payload"] def find_key(d: t.Any) -> Key: return guess_key(public_key, d) assert obj.signature is not None if not verify_signature(obj.member, obj.signature, payload_segment, registry, find_key): raise BadSignatureError() return obj def _extract_json(value: FlattenedJSONSerialization) -> t.Optional[FlattenedJSONSignature]: if "signatures" in value: return None if "protected" in value: protected_segment = to_bytes(value["protected"]) protected = json_b64decode(protected_segment) else: protected = None header = value.get("header") member = HeaderMember(protected, header) headers = member.headers() if "b64" not in headers: return None payload = to_bytes(value["payload"]) obj = FlattenedJSONSignature(member, payload) _sig: JSONSignatureDict = {"signature": value["signature"]} if "protected" in value: _sig["protected"] = value["protected"] if "header" in value: _sig["header"] = value["header"] obj.signature = _sig obj.segments = {"payload": payload} return obj joserfc-1.1.0/src/joserfc/rfc7797/registry.py000066400000000000000000000013711501424510600207200ustar00rootroot00000000000000from ..registry import JWS_HEADER_REGISTRY, Header, HeaderParameter from ..rfc7515.registry import JWSRegistry as _JWSRegistry class JWSRegistry(_JWSRegistry): default_header_registry = { "b64": HeaderParameter("JWS Signing Input Formula", "bool"), **JWS_HEADER_REGISTRY, } def check_header(self, header: Header) -> None: if "b64" in header: _safe_b64_header(header) super(JWSRegistry, self).check_header(header) def _safe_b64_header(header: Header) -> bool: # https://datatracker.ietf.org/doc/html/rfc7797#section-6 crit = header.get("crit") if isinstance(crit, list) and "b64" in crit: return True raise ValueError("The 'crit' Header Parameter MUST be included with 'b64'") joserfc-1.1.0/src/joserfc/rfc8037/000077500000000000000000000000001501424510600164605ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc8037/__init__.py000066400000000000000000000000001501424510600205570ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc8037/jws_eddsa.py000066400000000000000000000017411501424510600210000ustar00rootroot00000000000000from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey from ..rfc7515.model import JWSAlgModel from .okp_key import OKPKey class EdDSAAlgModel(JWSAlgModel): name = "EdDSA" description = "Edwards-curve Digital Signature Algorithm for JWS" key_type = "OKP" def sign(self, msg: bytes, key: OKPKey) -> bytes: op_key = key.get_op_key("sign") assert isinstance(op_key, (Ed25519PrivateKey, Ed448PrivateKey)) return op_key.sign(msg) def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool: op_key = key.get_op_key("verify") assert isinstance(op_key, (Ed25519PublicKey, Ed448PublicKey)) try: op_key.verify(sig, msg) return True except InvalidSignature: return False EdDSA = EdDSAAlgModel() joserfc-1.1.0/src/joserfc/rfc8037/okp_key.py000066400000000000000000000136501501424510600205000ustar00rootroot00000000000000from __future__ import annotations import typing as t from functools import cached_property from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PublicKey, X25519PrivateKey from cryptography.hazmat.primitives.asymmetric.x448 import X448PublicKey, X448PrivateKey from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, PrivateFormat, NoEncryption, ) from ..rfc7517.models import CurveKey from ..rfc7517.types import KeyParameters from ..rfc7517.pem import CryptographyBinding from ..errors import InvalidExchangeKeyError from ..util import to_bytes, urlsafe_b64decode, urlsafe_b64encode from ..registry import KeyParameter PublicOKPKey = t.Union[Ed25519PublicKey, Ed448PublicKey, X25519PublicKey, X448PublicKey] PrivateOKPKey = t.Union[Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey] OKPDictKey = t.TypedDict( "OKPDictKey", { "crv": t.Literal["Ed25519", "Ed448", "X25519", "X448"], "x": str, "d": str, }, total=False, ) PUBLIC_KEYS_MAP: t.Dict[str, t.Type[PublicOKPKey]] = { "Ed25519": Ed25519PublicKey, "Ed448": Ed448PublicKey, "X25519": X25519PublicKey, "X448": X448PublicKey, } PRIVATE_KEYS_MAP: t.Dict[str, t.Type[PrivateOKPKey]] = { "Ed25519": Ed25519PrivateKey, "Ed448": Ed448PrivateKey, "X25519": X25519PrivateKey, "X448": X448PrivateKey, } PrivateKeyTypes = (Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey) class OKPBinding(CryptographyBinding): ssh_type = b"ssh-ed25519" @staticmethod def import_private_key(obj: OKPDictKey) -> PrivateOKPKey: crv_key: t.Type[PrivateOKPKey] = PRIVATE_KEYS_MAP[obj["crv"]] d = urlsafe_b64decode(to_bytes(obj["d"])) return crv_key.from_private_bytes(d) @staticmethod def import_public_key(obj: OKPDictKey) -> PublicOKPKey: crv_key: t.Type[PublicOKPKey] = PUBLIC_KEYS_MAP[obj["crv"]] x_bytes = urlsafe_b64decode(to_bytes(obj["x"])) return crv_key.from_public_bytes(x_bytes) @staticmethod def export_private_key(key: PrivateOKPKey) -> t.Dict[str, str]: obj = OKPBinding.export_public_key(key.public_key()) d_bytes = key.private_bytes(Encoding.Raw, PrivateFormat.Raw, NoEncryption()) obj["d"] = urlsafe_b64encode(d_bytes).decode("utf-8") return obj @staticmethod def export_public_key(key: PublicOKPKey) -> t.Dict[str, str]: x_bytes = key.public_bytes(Encoding.Raw, PublicFormat.Raw) return { "crv": get_key_curve(key), "x": urlsafe_b64encode(x_bytes).decode("utf-8"), } class OKPKey(CurveKey[PrivateOKPKey, PublicOKPKey]): """Key class of the ``OKP`` key type.""" key_type = "OKP" #: Registry definition for OKP Key #: https://www.rfc-editor.org/rfc/rfc8037#section-2 value_registry = { "crv": KeyParameter("Curve", "str", private=False, required=True), "x": KeyParameter("X Coordinate", "str", private=False, required=True), "d": KeyParameter("OKP Private Key", "str", private=True, required=False), } binding = OKPBinding required_fields = frozenset(["crv", "x"]) private_only_fields = frozenset(["d"]) def exchange_derive_key(self, key: "OKPKey") -> bytes: # used in ECDH-ES Algorithms pubkey: t.Union[X25519PublicKey, X448PublicKey] = key.get_op_key("deriveKey") # type: ignore[assignment] if isinstance(self.private_key, X25519PrivateKey) and isinstance(pubkey, X25519PublicKey): return self.private_key.exchange(pubkey) elif isinstance(self.private_key, X448PrivateKey) and isinstance(pubkey, X448PublicKey): return self.private_key.exchange(pubkey) raise InvalidExchangeKeyError() @property def is_private(self) -> bool: return isinstance(self.raw_value, PrivateKeyTypes) @cached_property def public_key(self) -> PublicOKPKey: if isinstance(self.raw_value, PrivateKeyTypes): return self.raw_value.public_key() return self.raw_value @property def private_key(self) -> PrivateOKPKey | None: if isinstance(self.raw_value, PrivateKeyTypes): return self.raw_value return None @property def curve_name(self) -> str: return get_key_curve(self.raw_value) @classmethod def generate_key( cls, crv: str | None = "Ed25519", parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> "OKPKey": """Generate a ``OKPKey`` with the given "crv" value. :param crv: OKPKey curve name :param parameters: extra parameter in JWK :param private: generate a private key or public key :param auto_kid: add ``kid`` automatically """ if crv is None: crv = "Ed25519" if crv not in PRIVATE_KEYS_MAP: raise ValueError("Invalid crv value: '{}'".format(crv)) private_key_cls: t.Type[PrivateOKPKey] = PRIVATE_KEYS_MAP[crv] raw_key = private_key_cls.generate() if private: key = cls(raw_key, raw_key, parameters) else: pub_key = raw_key.public_key() key = cls(pub_key, pub_key, parameters) if auto_kid: key.ensure_kid() return key def get_key_curve(key: t.Union[PublicOKPKey, PrivateOKPKey]) -> str: if isinstance(key, (Ed25519PublicKey, Ed25519PrivateKey)): return "Ed25519" elif isinstance(key, (Ed448PublicKey, Ed448PrivateKey)): return "Ed448" elif isinstance(key, (X25519PublicKey, X25519PrivateKey)): return "X25519" elif isinstance(key, (X448PublicKey, X448PrivateKey)): return "X448" raise ValueError("Invalid key") # pragma: no cover joserfc-1.1.0/src/joserfc/rfc8812/000077500000000000000000000000001501424510600164615ustar00rootroot00000000000000joserfc-1.1.0/src/joserfc/rfc8812/__init__.py000066400000000000000000000006011501424510600205670ustar00rootroot00000000000000from cryptography.hazmat.primitives.asymmetric.ec import SECP256K1 from ..rfc7518.ec_key import ECKey from ..rfc7518.jws_algs import ECAlgModel ES256K = ECAlgModel("ES256K", "secp256k1", 256) def register_secp256k1() -> None: # https://tools.ietf.org/html/rfc8812#section-3.1 ECKey.binding.register_curve("secp256k1", SECP256K1) __all__ = ["ES256K", "register_secp256k1"] joserfc-1.1.0/src/joserfc/util.py000066400000000000000000000033311501424510600167130ustar00rootroot00000000000000from __future__ import annotations from typing import Any import base64 import struct import binascii import json def to_bytes(x: Any, charset: str = "utf-8", errors: str = "strict") -> bytes: if isinstance(x, bytes): return x if isinstance(x, str): return x.encode(charset, errors) if isinstance(x, (int, float)): return str(x).encode(charset, errors) return bytes(x) def to_str(x: bytes | str, charset: str = "utf-8") -> str: if isinstance(x, bytes): return x.decode(charset) return x def urlsafe_b64decode(s: bytes) -> bytes: if b"+" in s or b"/" in s: raise binascii.Error pad = -len(s) % 4 if pad == 3: raise binascii.Error safe_ending = (b"AEIMQUYcgkosw048", b"AQgw") if pad and s[-1] not in safe_ending[pad - 1]: raise binascii.Error s += b"=" * pad return base64.b64decode(s, b"-_", validate=True) def urlsafe_b64encode(s: bytes) -> bytes: return base64.urlsafe_b64encode(s).rstrip(b"=") def base64_to_int(s: str) -> int: data = urlsafe_b64decode(to_bytes(s)) buf = struct.unpack("%sB" % len(data), data) return int("".join(["%02x" % byte for byte in buf]), 16) def int_to_base64(num: int) -> str: if num < 0: raise ValueError("Must be a positive integer") s = num.to_bytes((num.bit_length() + 7) // 8, "big", signed=False) return urlsafe_b64encode(s).decode("utf-8", "strict") def json_b64encode(text: Any) -> bytes: if isinstance(text, dict): text = json.dumps(text, ensure_ascii=True, separators=(",", ":")) return urlsafe_b64encode(to_bytes(text, "ascii")) def json_b64decode(text: Any) -> Any: return json.loads(urlsafe_b64decode(to_bytes(text, "ascii"))) joserfc-1.1.0/tests/000077500000000000000000000000001501424510600143045ustar00rootroot00000000000000joserfc-1.1.0/tests/__init__.py000066400000000000000000000000001501424510600164030ustar00rootroot00000000000000joserfc-1.1.0/tests/base.py000066400000000000000000000026521501424510600155750ustar00rootroot00000000000000import json import typing as t from joserfc.jwk import import_key from unittest import TestCase from pathlib import Path BASE_PATH = Path(__file__).parent def read_fixture(filename: str): with open((BASE_PATH / "fixtures" / filename).resolve()) as f: return json.load(f) def load_key(filename: str, parameters=None): with open((BASE_PATH / "keys" / filename).resolve(), "rb") as f: content: bytes = f.read() if filename.endswith(".json"): data = json.loads(content) return import_key(data, parameters=parameters) kty = filename.split("-", 1)[0] return import_key(content, kty.upper(), parameters) class TestFixture(TestCase): @classmethod def load_fixture(cls, filename: str): fixture_data = read_fixture(filename) for case_data in fixture_data["tests"]: if "payload" not in case_data and "payload" in fixture_data: case_data["payload"] = fixture_data["payload"] cls.attach_case(case_data) @classmethod def attach_case(cls, data): runner = data.get("runner", "run_test") def method(self): getattr(self, runner)(data) case_name = data["name"] name = f"test_{case_name}" method.__name__ = name method.__doc__ = f"Run fixture {data}" setattr(cls, name, method) def run_test(self, data: t.Dict[str, t.Any]): raise NotImplementedError() joserfc-1.1.0/tests/fixtures/000077500000000000000000000000001501424510600161555ustar00rootroot00000000000000joserfc-1.1.0/tests/fixtures/jwe_compact_ecdh_1pu.json000066400000000000000000000100721501424510600231130ustar00rootroot00000000000000{ "payload": "hello", "tests": [ { "name": "ECDH-1PU and A128GCM", "alg": "ECDH-1PU", "enc": "A128GCM", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkExMjhHQ00iLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4IjoiSEtfbGRuaV9wV2JhV1ZUUEk5ZmpaZ3dCS2dDTkZYc3hPdTB1UGdPd0k3YyIsInkiOiJVOUd0ZG9GMmRSVV9CTWlvZTZ6N016dk9yaU40RXdCTkN3bGtHcmdMLTlnIiwia3R5IjoiRUMifX0..M82rjpR1Hfi5t_O2.nEG-yXA.lm5IeuqyS8T_I7SVSblEiA" }, { "name": "ECDH-1PU and A192GCM", "alg": "ECDH-1PU", "enc": "A192GCM", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkExOTJHQ00iLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4IjoiV3hfSlBYRzZTLUF6QjF0Rm4zUndNVlNtZlBYSjEwQ1MtYVVHNExQdWNUTSIsInkiOiJURmgxSjVadGZ5RUpTZFNlc1dUMUl5Wk1FSzBXNkI4N1Y4cmpsNEpKNS1VIiwia3R5IjoiRUMifX0..cPSG96y3fecbrqXc.y3Z3QGk.fjEpD8hYa-h4YOAxHMen2g" }, { "name": "ECDH-1PU and A256GCM", "alg": "ECDH-1PU", "enc": "A256GCM", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4IjoiODVNMVpiZGl0Tm5DSmJlMXlSa3lhSHd3V0s2cXlVb1VHeTZaa0hhVVRUZyIsInkiOiJRb3lMOGlPRlJNUVU4a29UOEZGOWtsTU5CbUlMWXVLX2hYVzNKc1NlcEw4Iiwia3R5IjoiRUMifX0..NUPVFQ75L-FqhwlU.-Ttl1E4.O0Y20OaYGtl26OCpkI7shg" }, { "name": "ECDH-1PU and A128CBC-HS256", "alg": "ECDH-1PU", "enc": "A128CBC-HS256", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkExMjhDQkMtSFMyNTYiLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4Ijoid2NoT3FjS1pOQndmZVVBWjNNTGVhZGxUU0VEQktYQngzUDFnMWNndm5MMCIsInkiOiJ4ODE4VEh3V1lfT2tmbzM1OVYwMVBOTi1YQ2JySS00TFV2U1dVM0xlQm1JIiwia3R5IjoiRUMifX0..Wd6vFQXUZy25aLqzzUU2EA.EcIvMfIRMhOMvoRsw0jNwg.7x4-HSkwgEHhSw-nBZb9rQ" }, { "name": "ECDH-1PU and A192CBC-HS384", "alg": "ECDH-1PU", "enc": "A192CBC-HS384", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkExOTJDQkMtSFMzODQiLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4IjoiRUJQaFpGZ3pkY19SZEo1RHlVenozbl8wS2dic3dlSHMwbTdZTXZ6SWxfcyIsInkiOiJwVkFyZzRnVVJIOW1mMHk5bnVUNEdJTzExRmJQZFJLM2ZlMDBFYmFoVkY4Iiwia3R5IjoiRUMifX0..wTTicHTGacdxcyRMSLfCkg.RE3HYXsGO624xVY_TB1iwA.G-6fFuAFvs-mwA4bQ4md6dP16Ul0DZ70" }, { "name": "ECDH-1PU and A256CBC-HS512", "alg": "ECDH-1PU", "enc": "A256CBC-HS512", "value": "eyJhbGciOiJFQ0RILTFQVSIsImVuYyI6IkEyNTZDQkMtSFM1MTIiLCJlcGsiOnsiY3J2IjoiUC0yNTYiLCJ4IjoiQW1XODN6ck85TWpiUnVCcUNwcUpidzNvWklvNG5OTTkzY05iS1pTV294NCIsInkiOiI0clBTQ0RreklkN0R2V250SzFQbjQzbGd0cGxINVMtQ0R6SXl0Skk2QkVnIiwia3R5IjoiRUMifX0..nSWZgOCgIXiEVCFVB9Rspg.RFNEVuXUynmJldtb3xAJhA.M3FeoReb-_MQ1h-xuvv7EaBFyLMang2tX4VptxHVCiU" }, { "name": "ECDH-1PU+A128KW and A128CBC-HS256", "alg": "ECDH-1PU+A128KW", "enc": "A128CBC-HS256", "value": "eyJhbGciOiJFQ0RILTFQVStBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiZXBrIjp7ImNydiI6IlAtMjU2IiwieCI6IlpPZ3BYblpPdG5Ra3BuajlxVWlVTnVHTjl0RGNCME02aGdLYURDYlJGQU0iLCJ5IjoieTQ2bGR0Ymw4UXJmS2tWUG9aTUF1cDNwX3JXM2gxeE8yc3RXdnJ0Z0ZQRSIsImt0eSI6IkVDIn19.QsB1-bCmnmGAuORH-yNWVZoKAPTGQCy82XJgtjXlTgCHWL7yWOBG1Q.v2BVmf9-ysHU5Ej39ULuEg.27HGkvRxAwAR5RSOqtvx2g.rIJt-IAK1Bjd3nlYYuv_tw" }, { "name": "ECDH-1PU+A128KW and A192CBC-HS384", "alg": "ECDH-1PU+A128KW", "enc": "A192CBC-HS384", "value": "eyJhbGciOiJFQ0RILTFQVStBMTI4S1ciLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiZXBrIjp7ImNydiI6IlAtMjU2IiwieCI6IlUwdVdqdVRXcUVreFVKSWw5WmF2ZlF3OGpjbjJiTExZZ1VKQnNzSjZxOTAiLCJ5IjoicXl3SXdvMmlsS0tjV0s1NF9XTFZ4QlM3ajdpMXd6anU1bkdreGtfanRQWSIsImt0eSI6IkVDIn19.ca8pq2DIOC687z6KpRuzZxVaXdhZ9SQEwKC2mqB4j8v3dfjiBT0aK-XBPRaxh8QZJu2zbHQ1y0E.-gtKdxUQNzacai2ISKEUbw.G_AkK9-anlNQ00G-e6QvMw.GdZnKpeK9-DhHEZi4ejhEYgnbi5HK-f4" }, { "name": "ECDH-1PU+A128KW and A256CBC-HS512", "alg": "ECDH-1PU+A128KW", "enc": "A256CBC-HS512", "value": "eyJhbGciOiJFQ0RILTFQVStBMTI4S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiZXBrIjp7ImNydiI6IlAtMjU2IiwieCI6Im1JWlVWckNqV1A4X3l2Y2VNeW9tdGc2TmZ6alNkeE03MVFjQWd0MFVXTGMiLCJ5IjoiaWVGbkd1cGNKOUdSY2VzbDJWaHJLQW9MYzFoN1pteDI0OGcwNng2UWxjNCIsImt0eSI6IkVDIn19.bb8Mil28nTufsGiTyfzYcHt2siSoVK_U3S3OnhqwmfqMJX-AWf38p3EMDtNszUPonXgcMvuPa2mvKN4lJjxUjja3Mf6SWkXu.1J_WYNm3VssfOHstFNYCig.68Y7E3FiMjI_uIANYVer-Q.P8qBqH0NJeEvZhlWVdTSpSeI_0vXmFanqn7RBci6VHU" } ] } joserfc-1.1.0/tests/fixtures/jwe_compact_ecdh_es.json000066400000000000000000000000001501424510600230030ustar00rootroot00000000000000joserfc-1.1.0/tests/fixtures/jwe_rfc7520.json000066400000000000000000000337611501424510600210170ustar00rootroot00000000000000{ "payload": "", "tests": [ { "name": "Key Encryption Using RSA v1.5 and AES-HMAC-SHA2", "key": "RFC7520-RSA-73.json", "protected": { "alg": "RSA1_5", "kid": "frodo.baggins@hobbiton.example", "enc": "A128CBC-HS256" }, "compact": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePFvG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2GXfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcGTSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8VlzNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOhMBs9M8XL223Fg47xlGsMXdfuY-4jaqVw.bbd5sTkYwhAIqfHsx8DayA.0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_raa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8OWzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZVyeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VWi7lzA6BP430m.kvKuFBXHe5mQr4lqgobAUg", "general_json": { "recipients": [ { "encrypted_key": "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePFvG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2GXfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcGTSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8VlzNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOhMBs9M8XL223Fg47xlGsMXdfuY-4jaqVw" } ], "protected": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", "iv": "bbd5sTkYwhAIqfHsx8DayA", "ciphertext": "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_raa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8OWzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZVyeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VWi7lzA6BP430m", "tag": "kvKuFBXHe5mQr4lqgobAUg" }, "flattened_json": { "protected": "eyJhbGciOiJSU0ExXzUiLCJraWQiOiJmcm9kby5iYWdnaW5zQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", "encrypted_key": "laLxI0j-nLH-_BgLOXMozKxmy9gffy2gTdvqzfTihJBuuzxg0V7yk1WClnQePFvG2K-pvSlWc9BRIazDrn50RcRai__3TDON395H3c62tIouJJ4XaRvYHFjZTZ2GXfz8YAImcc91Tfk0WXC2F5Xbb71ClQ1DDH151tlpH77f2ff7xiSxh9oSewYrcGTSLUeeCt36r1Kt3OSj7EyBQXoZlN7IxbyhMAfgIe7Mv1rOTOI5I8NQqeXXW8VlzNmoxaGMny3YnGir5Wf6Qt2nBq4qDaPdnaAuuGUGEecelIO1wx1BpyIfgvfjOhMBs9M8XL223Fg47xlGsMXdfuY-4jaqVw", "iv": "bbd5sTkYwhAIqfHsx8DayA", "ciphertext": "0fys_TY_na7f8dwSfXLiYdHaA2DxUjD67ieF7fcVbIR62JhJvGZ4_FNVSiGc_raa0HnLQ6s1P2sv3Xzl1p1l_o5wR_RsSzrS8Z-wnI3Jvo0mkpEEnlDmZvDu_k8OWzJv7eZVEqiWKdyVzFhPpiyQU28GLOpRc2VbVbK4dQKPdNTjPPEmRqcaGeTWZVyeSUvf5k59yJZxRuSvWFf6KrNtmRdZ8R4mDOjHSrM_s8uwIFcqt4r5GX8TKaI0zT5CbL5Qlw3sRc7u_hg0yKVOiRytEAEs3vZkcfLkP6nbXdC_PkMdNS-ohP78T2O6_7uInMGhFeX4ctHG7VelHGiT93JfWDEQi5_V9UN1rhXNrYu-0fVMkZAKX3VWi7lzA6BP430m", "tag": "kvKuFBXHe5mQr4lqgobAUg" } }, { "name": "Key Encryption Using RSA-OAEP with AES-GCM", "key": "RFC7520-RSA-84.json", "protected": { "alg": "RSA-OAEP", "kid": "samwise.gamgee@hobbiton.example", "enc": "A256GCM" }, "compact": "eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0.rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQibeYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyucvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58-Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8BpxKdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pKIIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQfOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe38UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDRs.-nBoKLH0YkLZPSI9.o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgRL-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEwP7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSVmaPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw.UCGiqJxhBI3IFVdPalHHvA", "general_json": { "recipients": [ { "encrypted_key": "rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQibeYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyucvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58-Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8BpxKdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pKIIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQfOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe38UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDRs" } ], "protected": "eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0", "iv": "-nBoKLH0YkLZPSI9", "ciphertext": "o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgRL-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEwP7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSVmaPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw", "tag": "UCGiqJxhBI3IFVdPalHHvA" }, "flattened_json": { "protected": "eyJhbGciOiJSU0EtT0FFUCIsImtpZCI6InNhbXdpc2UuZ2FtZ2VlQGhvYmJpdG9uLmV4YW1wbGUiLCJlbmMiOiJBMjU2R0NNIn0", "encrypted_key": "rT99rwrBTbTI7IJM8fU3Eli7226HEB7IchCxNuh7lCiud48LxeolRdtFF4nzQibeYOl5S_PJsAXZwSXtDePz9hk-BbtsTBqC2UsPOdwjC9NhNupNNu9uHIVftDyucvI6hvALeZ6OGnhNV4v1zx2k7O1D89mAzfw-_kT3tkuorpDU-CpBENfIHX1Q58-Aad3FzMuo3Fn9buEP2yXakLXYa15BUXQsupM4A1GD4_H4Bd7V3u9h8Gkg8BpxKdUV9ScfJQTcYm6eJEBz3aSwIaK4T3-dwWpuBOhROQXBosJzS1asnuHtVMt2pKIIfux5BC6huIvmY7kzV7W7aIUrpYm_3H4zYvyMeq5pGqFmW2k8zpO878TRlZx7pZfPYDSXZyS0CfKKkMozT_qiCwZTSz4duYnt8hS4Z9sGthXn9uDqd6wycMagnQfOTs_lycTWmY-aqWVDKhjYNRf03NiwRtb5BE-tOdFwCASQj3uuAgPGrO2AWBe38UjQb0lvXn1SpyvYZ3WFc7WOJYaTa7A8DRn6MC6T-xDmMuxC0G7S2rscw5lQQU06MvZTlFOt0UvfuKBa03cxA_nIBIhLMjY2kOTxQMmpDPTr6Cbo8aKaOnx6ASE5Jx9paBpnNmOOKH35j_QlrQhDWUN6A2Gg8iFayJ69xDEdHAVCGRzN3woEI2ozDRs", "iv": "-nBoKLH0YkLZPSI9", "ciphertext": "o4k2cnGN8rSSw3IDo1YuySkqeS_t2m1GXklSgqBdpACm6UJuJowOHC5ytjqYgRL-I-soPlwqMUf4UgRWWeaOGNw6vGW-xyM01lTYxrXfVzIIaRdhYtEMRBvBWbEwP7ua1DRfvaOjgZv6Ifa3brcAM64d8p5lhhNcizPersuhw5f-pGYzseva-TUaL8iWnctc-sSwy7SQmRkfhDjwbz0fz6kFovEgj64X1I5s7E6GLp5fnbYGLa1QUiML7Cc2GxgvI7zqWo0YIEc7aCflLG1-8BboVWFdZKLK9vNoycrYHumwzKluLWEbSVmaPpOslY2n525DxDfWaVFUfKQxMF56vn4B9QMpWAbnypNimbM8zVOw", "tag": "UCGiqJxhBI3IFVdPalHHvA" } }, { "name": "Key Agreement with Key Wrapping Using ECDH-ES and AES-KeyWrap with AES-GCM", "key": "RFC7520-EC-108.json", "runner": "run_test_agreement", "epk": { "kty": "EC", "crv": "P-384", "x": "uBo4kHPw6kbjx5l0xowrd_oYzBmaz-GKFZu4xAFFkbYiWgutEK6iuEDsQ6wNdNg3", "y": "sp3p5SGhZVC2faXumI-e9JU2Mo8KpoYrFDr5yPNVtW4PgEwZOyQTA-JdaY8tb7E0", "d": "D5H4Y_5PSKZvhfVFbcCYJOtcGZygRgfZkpsBr59Icmmhe9sW6nkZ8WfwhinUfWJg" }, "protected": { "alg": "ECDH-ES+A128KW", "kid": "peregrin.took@tuckborough.example", "enc": "A128GCM" }, "compact": "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdHVja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NHhBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMyZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWTh0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0.0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2.mH-G2zVqgztUtnW_.tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cPWJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkcY9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w03XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ.WuGzxmcreYjpHGJoa17EBg", "general_json": { "recipients": [ { "encrypted_key": "0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2" } ], "protected": "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdHVja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NHhBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMyZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWTh0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0", "iv": "mH-G2zVqgztUtnW_", "ciphertext": "tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cPWJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkcY9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w03XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ", "tag": "WuGzxmcreYjpHGJoa17EBg" }, "flattened_json": { "protected": "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImtpZCI6InBlcmVncmluLnRvb2tAdHVja2Jvcm91Z2guZXhhbXBsZSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0IiwieCI6InVCbzRrSFB3Nmtiang1bDB4b3dyZF9vWXpCbWF6LUdLRlp1NHhBRkZrYllpV2d1dEVLNml1RURzUTZ3TmROZzMiLCJ5Ijoic3AzcDVTR2haVkMyZmFYdW1JLWU5SlUyTW84S3BvWXJGRHI1eVBOVnRXNFBnRXdaT3lRVEEtSmRhWTh0YjdFMCJ9LCJlbmMiOiJBMTI4R0NNIn0", "encrypted_key": "0DJjBXri_kBcC46IkU5_Jk9BqaQeHdv2", "iv": "mH-G2zVqgztUtnW_", "ciphertext": "tkZuOO9h95OgHJmkkrfLBisku8rGf6nzVxhRM3sVOhXgz5NJ76oID7lpnAi_cPWJRCjSpAaUZ5dOR3Spy7QuEkmKx8-3RCMhSYMzsXaEwDdXta9Mn5B7cCBoJKB0IgEnj_qfo1hIi-uEkUpOZ8aLTZGHfpl05jMwbKkTe2yK3mjF6SBAsgicQDVCkcY9BLluzx1RmC3ORXaM0JaHPB93YcdSDGgpgBWMVrNU1ErkjcMqMoT_wtCex3w03XdLkjXIuEr2hWgeP-nkUZTPU9EoGSPj6fAS-bSz87RCPrxZdj_iVyC6QWcqAu07WNhjzJEPc4jVntRJ6K53NgPQ5p99l3Z408OUqj4ioYezbS6vTPlQ", "tag": "WuGzxmcreYjpHGJoa17EBg" } }, { "name": "Key Agreement Using ECDH-ES with AES-CBC-HMAC-SHA2", "key": "RFC7520-EC-120.json", "runner": "run_test_agreement", "epk": { "kty": "EC", "crv": "P-256", "x": "mPUKT_bAWGHIhg0TpjjqVsP1rXWQu_vwVOHHtNkdYoA", "y": "8BQAsImGeAS46fyWw5MhYfGTT0IjBpFw2SS34Dv4Irs", "d": "AtH35vJsQ9SGjYfOsjUxYXQKrPH3FjZHmEtSKoSN8cM" }, "protected": { "alg": "ECDH-ES", "kid": "meriadoc.brandybuck@buckland.example", "enc": "A128CBC-HS256" }, "compact": "eyJhbGciOiJFQ0RILUVTIiwia2lkIjoibWVyaWFkb2MuYnJhbmR5YnVja0BidWNrbGFuZC5leGFtcGxlIiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoibVBVS1RfYkFXR0hJaGcwVHBqanFWc1AxclhXUXVfdndWT0hIdE5rZFlvQSIsInkiOiI4QlFBc0ltR2VBUzQ2ZnlXdzVNaFlmR1RUMElqQnBGdzJTUzM0RHY0SXJzIn0sImVuYyI6IkExMjhDQkMtSFMyNTYifQ..yc9N8v5sYyv3iGQT926IUg.BoDlwPnTypYq-ivjmQvAYJLb5Q6l-F3LIgQomlz87yW4OPKbWE1zSTEFjDfhU9IPIOSA9Bml4m7iDFwA-1ZXvHteLDtw4R1XRGMEsDIqAYtskTTmzmzNa-_q4F_evAPUmwlO-ZG45Mnq4uhM1fm_D9rBtWolqZSF3xGNNkpOMQKF1Cl8i8wjzRli7-IXgyirlKQsbhhqRzkv8IcY6aHl24j03C-AR2le1r7URUhArM79BY8soZU0lzwI-sD5PZ3l4NDCCei9XkoIAfsXJWmySPoeRb2Ni5UZL4mYpvKDiwmyzGd65KqVw7MsFfI_K767G9C9Azp73gKZD0DyUn1mn0WW5LmyX_yJ-3AROq8p1WZBfG-ZyJ6195_JGG2m9Csg.WCCkNa-x4BeB9hIDIfFuhg", "general_json": { "protected": "eyJhbGciOiJFQ0RILUVTIiwia2lkIjoibWVyaWFkb2MuYnJhbmR5YnVja0BidWNrbGFuZC5leGFtcGxlIiwiZXBrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoibVBVS1RfYkFXR0hJaGcwVHBqanFWc1AxclhXUXVfdndWT0hIdE5rZFlvQSIsInkiOiI4QlFBc0ltR2VBUzQ2ZnlXdzVNaFlmR1RUMElqQnBGdzJTUzM0RHY0SXJzIn0sImVuYyI6IkExMjhDQkMtSFMyNTYifQ", "iv": "yc9N8v5sYyv3iGQT926IUg", "ciphertext": "BoDlwPnTypYq-ivjmQvAYJLb5Q6l-F3LIgQomlz87yW4OPKbWE1zSTEFjDfhU9IPIOSA9Bml4m7iDFwA-1ZXvHteLDtw4R1XRGMEsDIqAYtskTTmzmzNa-_q4F_evAPUmwlO-ZG45Mnq4uhM1fm_D9rBtWolqZSF3xGNNkpOMQKF1Cl8i8wjzRli7-IXgyirlKQsbhhqRzkv8IcY6aHl24j03C-AR2le1r7URUhArM79BY8soZU0lzwI-sD5PZ3l4NDCCei9XkoIAfsXJWmySPoeRb2Ni5UZL4mYpvKDiwmyzGd65KqVw7MsFfI_K767G9C9Azp73gKZD0DyUn1mn0WW5LmyX_yJ-3AROq8p1WZBfG-ZyJ6195_JGG2m9Csg", "tag": "WCCkNa-x4BeB9hIDIfFuhg" } }, { "name": "Direct Encryption Using AES-GCM", "key": "RFC7520-oct-130.json", "protected": { "alg": "dir", "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", "enc": "A128GCM" }, "compact": "eyJhbGciOiJkaXIiLCJraWQiOiI3N2M3ZTJiOC02ZTEzLTQ1Y2YtODY3Mi02MTdiNWI0NTI0M2EiLCJlbmMiOiJBMTI4R0NNIn0..refa467QzzKx6QAB.JW_i_f52hww_ELQPGaYyeAB6HYGcR559l9TYnSovc23XJoBcW29rHP8yZOZG7YhLpT1bjFuvZPjQS-m0IFtVcXkZXdH_lr_FrdYt9HRUYkshtrMmIUAyGmUnd9zMDB2n0cRDIHAzFVeJUDxkUwVAE7_YGRPdcqMyiBoCO-FBdE-Nceb4h3-FtBP-c_BIwCPTjb9o0SbdcdREEMJMyZBH8ySWMVi1gPD9yxi-aQpGbSv_F9N4IZAxscj5g-NJsUPbjk29-s7LJAGb15wEBtXphVCgyy53CoIKLHHeJHXex45Uz9aKZSRSInZI-wjsY0yu3cT4_aQ3i1o-tiE-F8Ios61EKgyIQ4CWao8PFMj8TTnp.vbb32Xvllea2OtmHAdccRQ", "flattened_json": { "protected": "eyJhbGciOiJkaXIiLCJraWQiOiI3N2M3ZTJiOC02ZTEzLTQ1Y2YtODY3Mi02MTdiNWI0NTI0M2EiLCJlbmMiOiJBMTI4R0NNIn0", "iv": "refa467QzzKx6QAB", "ciphertext": "JW_i_f52hww_ELQPGaYyeAB6HYGcR559l9TYnSovc23XJoBcW29rHP8yZOZG7YhLpT1bjFuvZPjQS-m0IFtVcXkZXdH_lr_FrdYt9HRUYkshtrMmIUAyGmUnd9zMDB2n0cRDIHAzFVeJUDxkUwVAE7_YGRPdcqMyiBoCO-FBdE-Nceb4h3-FtBP-c_BIwCPTjb9o0SbdcdREEMJMyZBH8ySWMVi1gPD9yxi-aQpGbSv_F9N4IZAxscj5g-NJsUPbjk29-s7LJAGb15wEBtXphVCgyy53CoIKLHHeJHXex45Uz9aKZSRSInZI-wjsY0yu3cT4_aQ3i1o-tiE-F8Ios61EKgyIQ4CWao8PFMj8TTnp", "tag": "vbb32Xvllea2OtmHAdccRQ" } } ] } joserfc-1.1.0/tests/fixtures/jws_examples.json000066400000000000000000000264611501424510600215620ustar00rootroot00000000000000{ "payload": "hello", "tests": [ { "name": "HS256", "secret": "rfc", "protected": { "alg": "HS256" }, "compact": "eyJhbGciOiJIUzI1NiJ9.aGVsbG8.92P-6BQfptptqR5ESrsFD2Zv31kczcmHOR6eQXIaxVE", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJIUzI1NiJ9", "signature": "92P-6BQfptptqR5ESrsFD2Zv31kczcmHOR6eQXIaxVE" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJIUzI1NiJ9", "signature": "92P-6BQfptptqR5ESrsFD2Zv31kczcmHOR6eQXIaxVE" } }, { "name": "HS384", "secret": "rfc", "protected": { "alg": "HS384" }, "compact": "eyJhbGciOiJIUzM4NCJ9.aGVsbG8.LIiSePQNBOB6KwvO6EWcnfF6QC2lkijalXBokVRzNSltOmTSI3ujNBPqADnMaTvb", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJIUzM4NCJ9", "signature": "LIiSePQNBOB6KwvO6EWcnfF6QC2lkijalXBokVRzNSltOmTSI3ujNBPqADnMaTvb" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJIUzM4NCJ9", "signature": "LIiSePQNBOB6KwvO6EWcnfF6QC2lkijalXBokVRzNSltOmTSI3ujNBPqADnMaTvb" } }, { "name": "HS512", "secret": "rfc", "protected": { "alg": "HS512" }, "compact": "eyJhbGciOiJIUzUxMiJ9.aGVsbG8.QN5Ic-wF0VAKSpTjIlSqxYSS0Th6hiiDRoBVjqOweUmYsqZ5qM8jIez77l1rXxLycyWqrhzfwVvwrAdCBzCm1Q", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJIUzUxMiJ9", "signature": "QN5Ic-wF0VAKSpTjIlSqxYSS0Th6hiiDRoBVjqOweUmYsqZ5qM8jIez77l1rXxLycyWqrhzfwVvwrAdCBzCm1Q" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJIUzUxMiJ9", "signature": "QN5Ic-wF0VAKSpTjIlSqxYSS0Th6hiiDRoBVjqOweUmYsqZ5qM8jIez77l1rXxLycyWqrhzfwVvwrAdCBzCm1Q" } }, { "name": "RS256", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "RS256" }, "compact": "eyJhbGciOiJSUzI1NiJ9.aGVsbG8.GVU8F9Ygp1fzFg8R1-cBj-o2x5cNVrbmuqGzw7haatCVBzVX4xojYkpxgr7Mpy59_XN9uJ2PAwmK6r3_7f1_wnT-lfgYXCuD-CdhNvkSCmaoAFivEGuU8i-5KdmfH0NFbNYFd2vyuFXPDAGzgydZsBOZt60FHMc06K_ddrTCPe2r7RMeJDksMInIIipI7wthqi6xug0E2WBow37-mu-dnvZ2a0W5w6MfhE5EM8oE8qVZ92jjNuEWpMrmD1in2x98LiZWvOIbkIUqh-rJZ_akMY8x4vIJ22TWNS9WjVW3TWvHpNWZWik-CfIYku1xPI1FZZN46RNVwCsGH3muBkG8Ok83p3ylu_Zz5H8UDZ9YhSV8_GjLBKE6lKujZ1NtbKDfm0sxPKMh2Mq1y4je6OD_VC87Ya7UelBUXGjK69_LxVXLBhPm9Rly6k9FemD0Di6zmNJZ4hnGHjIDkPqrGFBCe_s8Ve8Pltk5MFoPPN5zcDqB-D9n-w0WazOEYXP59WmTxA_nhKsqiunyWDXIYV6ThaJ12gUvOvnTndFCx84j4wbnglnbsVSwgOPojdgGflb7XgljV47lT-DW_BoBbBaC6lhSsnKBU28z89hXIfJfg1OqiGntAtt5duHWdRmFjaCX6udzOw7sIYnFXsS5LD2f8lo9TmgGO2JBG3mPiLA65JQ", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJSUzI1NiJ9", "signature": "GVU8F9Ygp1fzFg8R1-cBj-o2x5cNVrbmuqGzw7haatCVBzVX4xojYkpxgr7Mpy59_XN9uJ2PAwmK6r3_7f1_wnT-lfgYXCuD-CdhNvkSCmaoAFivEGuU8i-5KdmfH0NFbNYFd2vyuFXPDAGzgydZsBOZt60FHMc06K_ddrTCPe2r7RMeJDksMInIIipI7wthqi6xug0E2WBow37-mu-dnvZ2a0W5w6MfhE5EM8oE8qVZ92jjNuEWpMrmD1in2x98LiZWvOIbkIUqh-rJZ_akMY8x4vIJ22TWNS9WjVW3TWvHpNWZWik-CfIYku1xPI1FZZN46RNVwCsGH3muBkG8Ok83p3ylu_Zz5H8UDZ9YhSV8_GjLBKE6lKujZ1NtbKDfm0sxPKMh2Mq1y4je6OD_VC87Ya7UelBUXGjK69_LxVXLBhPm9Rly6k9FemD0Di6zmNJZ4hnGHjIDkPqrGFBCe_s8Ve8Pltk5MFoPPN5zcDqB-D9n-w0WazOEYXP59WmTxA_nhKsqiunyWDXIYV6ThaJ12gUvOvnTndFCx84j4wbnglnbsVSwgOPojdgGflb7XgljV47lT-DW_BoBbBaC6lhSsnKBU28z89hXIfJfg1OqiGntAtt5duHWdRmFjaCX6udzOw7sIYnFXsS5LD2f8lo9TmgGO2JBG3mPiLA65JQ" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJSUzI1NiJ9", "signature": "GVU8F9Ygp1fzFg8R1-cBj-o2x5cNVrbmuqGzw7haatCVBzVX4xojYkpxgr7Mpy59_XN9uJ2PAwmK6r3_7f1_wnT-lfgYXCuD-CdhNvkSCmaoAFivEGuU8i-5KdmfH0NFbNYFd2vyuFXPDAGzgydZsBOZt60FHMc06K_ddrTCPe2r7RMeJDksMInIIipI7wthqi6xug0E2WBow37-mu-dnvZ2a0W5w6MfhE5EM8oE8qVZ92jjNuEWpMrmD1in2x98LiZWvOIbkIUqh-rJZ_akMY8x4vIJ22TWNS9WjVW3TWvHpNWZWik-CfIYku1xPI1FZZN46RNVwCsGH3muBkG8Ok83p3ylu_Zz5H8UDZ9YhSV8_GjLBKE6lKujZ1NtbKDfm0sxPKMh2Mq1y4je6OD_VC87Ya7UelBUXGjK69_LxVXLBhPm9Rly6k9FemD0Di6zmNJZ4hnGHjIDkPqrGFBCe_s8Ve8Pltk5MFoPPN5zcDqB-D9n-w0WazOEYXP59WmTxA_nhKsqiunyWDXIYV6ThaJ12gUvOvnTndFCx84j4wbnglnbsVSwgOPojdgGflb7XgljV47lT-DW_BoBbBaC6lhSsnKBU28z89hXIfJfg1OqiGntAtt5duHWdRmFjaCX6udzOw7sIYnFXsS5LD2f8lo9TmgGO2JBG3mPiLA65JQ" } }, { "name": "RS384", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "RS384" }, "compact": "eyJhbGciOiJSUzM4NCJ9.aGVsbG8.c647l2lFKP9gnmWQdBICAvmiTugZleIT8hAWVBhCcgiOBzyUS0HECbkmre2egUSbtl8PL2OlXqJER26641G2zVbCQqWvCG_9HxgbLmfB7980voN860yzyKYqSRMuxM9P_a6ZR4PBho6Ng63T4XStz8JB3v3vHnq2FjtJfhzmHSRUhtsbo4u9anqKyINfgfDh0BCfCGny6gXcXEyHOo0zaerhOpkq-qcp3sEmaPWzLvpF4IL6gv3tMgyfMBdDONGpb_UUYiJNnzEN1LDW39Sg3muewRekFv7DwuHJOCMYi_zipOlcMM4ONRXE-T9krvf2pIc4mVHei2hG2WYTV2yOnr-wx7J8WlbPmK0_GwImGoult9hov8ihPTBun9wp374WcYn8lcjkCYz7eNYoF5Vrl_HVc0EyTuM8Bf4qStEmnIWCSlgwMgzEOqwf1mi6Oyh_iZh_wxHuklacP7iLsNtAFpfoCEy0s8Z66rHALYBPWYg95rgKw1qS1r652AS_AhYCUc9HPew84JTdngeUX3uJv_sOEWscS9Cr9-RqeZkdTQvHW51zafKy-m53yZ2bhUlhBR6MgZ7J-uN6xiLlC82DlBlDq3P8Hz96q5EoabJFXwCtz2Q6VnHk7DmOEFC5lESCcGDaTYdPaLHuJno12zxjTzZ_kUkzQSwKEzSkWfPesKY", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJSUzM4NCJ9", "signature": "c647l2lFKP9gnmWQdBICAvmiTugZleIT8hAWVBhCcgiOBzyUS0HECbkmre2egUSbtl8PL2OlXqJER26641G2zVbCQqWvCG_9HxgbLmfB7980voN860yzyKYqSRMuxM9P_a6ZR4PBho6Ng63T4XStz8JB3v3vHnq2FjtJfhzmHSRUhtsbo4u9anqKyINfgfDh0BCfCGny6gXcXEyHOo0zaerhOpkq-qcp3sEmaPWzLvpF4IL6gv3tMgyfMBdDONGpb_UUYiJNnzEN1LDW39Sg3muewRekFv7DwuHJOCMYi_zipOlcMM4ONRXE-T9krvf2pIc4mVHei2hG2WYTV2yOnr-wx7J8WlbPmK0_GwImGoult9hov8ihPTBun9wp374WcYn8lcjkCYz7eNYoF5Vrl_HVc0EyTuM8Bf4qStEmnIWCSlgwMgzEOqwf1mi6Oyh_iZh_wxHuklacP7iLsNtAFpfoCEy0s8Z66rHALYBPWYg95rgKw1qS1r652AS_AhYCUc9HPew84JTdngeUX3uJv_sOEWscS9Cr9-RqeZkdTQvHW51zafKy-m53yZ2bhUlhBR6MgZ7J-uN6xiLlC82DlBlDq3P8Hz96q5EoabJFXwCtz2Q6VnHk7DmOEFC5lESCcGDaTYdPaLHuJno12zxjTzZ_kUkzQSwKEzSkWfPesKY" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJSUzM4NCJ9", "signature": "c647l2lFKP9gnmWQdBICAvmiTugZleIT8hAWVBhCcgiOBzyUS0HECbkmre2egUSbtl8PL2OlXqJER26641G2zVbCQqWvCG_9HxgbLmfB7980voN860yzyKYqSRMuxM9P_a6ZR4PBho6Ng63T4XStz8JB3v3vHnq2FjtJfhzmHSRUhtsbo4u9anqKyINfgfDh0BCfCGny6gXcXEyHOo0zaerhOpkq-qcp3sEmaPWzLvpF4IL6gv3tMgyfMBdDONGpb_UUYiJNnzEN1LDW39Sg3muewRekFv7DwuHJOCMYi_zipOlcMM4ONRXE-T9krvf2pIc4mVHei2hG2WYTV2yOnr-wx7J8WlbPmK0_GwImGoult9hov8ihPTBun9wp374WcYn8lcjkCYz7eNYoF5Vrl_HVc0EyTuM8Bf4qStEmnIWCSlgwMgzEOqwf1mi6Oyh_iZh_wxHuklacP7iLsNtAFpfoCEy0s8Z66rHALYBPWYg95rgKw1qS1r652AS_AhYCUc9HPew84JTdngeUX3uJv_sOEWscS9Cr9-RqeZkdTQvHW51zafKy-m53yZ2bhUlhBR6MgZ7J-uN6xiLlC82DlBlDq3P8Hz96q5EoabJFXwCtz2Q6VnHk7DmOEFC5lESCcGDaTYdPaLHuJno12zxjTzZ_kUkzQSwKEzSkWfPesKY" } }, { "name": "RS512", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "RS512" }, "compact": "eyJhbGciOiJSUzUxMiJ9.aGVsbG8.jIms9o3_R0NzcDSLcC4-HBWnEgm35Wepq8Fu_2C1Sv-nwdkLg6j7dtFMKfoDjxar7fCAjf_JJ_9Z7JTAJzDiFitNnFXuRpGSBm5ZOIYgIMOWl1cjToTe-FJS8T3mXxa4c0wz7a-E9pa4PLAB_Isgx0Hg-7CeH0hpKqULuUIvxuwsAeiKQeZpqu-KjAdFXp7AiJLFnLr7NzcelFsyzwcIVqB7GcDXpkCdmxKHUfOF8iN67lngqghl6lqWAVxjwGXm28mDrRbWnCOxFAZrgiDGkvoyImXMPywOQ7fioffgUTYa54Gmd63JH3L2YIlp2bX-Z9KlstDf9J2igwHQjcidk486f2e9ip0FI0cUa9w9_RJag2-loxkJjvsG6ial6LiiAA8qQZCMoVGcYJsAXhJfVGiLtxVj5v6iSUu5cSEoxI23QeAwIpvXWMiyFvWwwvalv3p-7JKWtbqFRWvx2LjCJpKAEHcRpVEx-brxsNGMH4gL648RFPn8-yFVsvb6DHOLP4VKEOVQhGW5KoPSafp2myyhTzHC8SpmwEJP0y0ArXfXngKs_eHTKRgyvPR2PJEtsoBzOyvF3h6BbsXbvhAVTT-k8qf0jqU4TscoCDPIbiJ9yo6Y_IFCu14zYHBxVZsED4V_v_Cwd3s935F8vNyqwddSm74izC8maNSYu34f38s", "general_json": { "payload": "aGVsbG8", "signatures": [ { "protected": "eyJhbGciOiJSUzUxMiJ9", "signature": "jIms9o3_R0NzcDSLcC4-HBWnEgm35Wepq8Fu_2C1Sv-nwdkLg6j7dtFMKfoDjxar7fCAjf_JJ_9Z7JTAJzDiFitNnFXuRpGSBm5ZOIYgIMOWl1cjToTe-FJS8T3mXxa4c0wz7a-E9pa4PLAB_Isgx0Hg-7CeH0hpKqULuUIvxuwsAeiKQeZpqu-KjAdFXp7AiJLFnLr7NzcelFsyzwcIVqB7GcDXpkCdmxKHUfOF8iN67lngqghl6lqWAVxjwGXm28mDrRbWnCOxFAZrgiDGkvoyImXMPywOQ7fioffgUTYa54Gmd63JH3L2YIlp2bX-Z9KlstDf9J2igwHQjcidk486f2e9ip0FI0cUa9w9_RJag2-loxkJjvsG6ial6LiiAA8qQZCMoVGcYJsAXhJfVGiLtxVj5v6iSUu5cSEoxI23QeAwIpvXWMiyFvWwwvalv3p-7JKWtbqFRWvx2LjCJpKAEHcRpVEx-brxsNGMH4gL648RFPn8-yFVsvb6DHOLP4VKEOVQhGW5KoPSafp2myyhTzHC8SpmwEJP0y0ArXfXngKs_eHTKRgyvPR2PJEtsoBzOyvF3h6BbsXbvhAVTT-k8qf0jqU4TscoCDPIbiJ9yo6Y_IFCu14zYHBxVZsED4V_v_Cwd3s935F8vNyqwddSm74izC8maNSYu34f38s" } ] }, "flattened_json": { "payload": "aGVsbG8", "protected": "eyJhbGciOiJSUzUxMiJ9", "signature": "jIms9o3_R0NzcDSLcC4-HBWnEgm35Wepq8Fu_2C1Sv-nwdkLg6j7dtFMKfoDjxar7fCAjf_JJ_9Z7JTAJzDiFitNnFXuRpGSBm5ZOIYgIMOWl1cjToTe-FJS8T3mXxa4c0wz7a-E9pa4PLAB_Isgx0Hg-7CeH0hpKqULuUIvxuwsAeiKQeZpqu-KjAdFXp7AiJLFnLr7NzcelFsyzwcIVqB7GcDXpkCdmxKHUfOF8iN67lngqghl6lqWAVxjwGXm28mDrRbWnCOxFAZrgiDGkvoyImXMPywOQ7fioffgUTYa54Gmd63JH3L2YIlp2bX-Z9KlstDf9J2igwHQjcidk486f2e9ip0FI0cUa9w9_RJag2-loxkJjvsG6ial6LiiAA8qQZCMoVGcYJsAXhJfVGiLtxVj5v6iSUu5cSEoxI23QeAwIpvXWMiyFvWwwvalv3p-7JKWtbqFRWvx2LjCJpKAEHcRpVEx-brxsNGMH4gL648RFPn8-yFVsvb6DHOLP4VKEOVQhGW5KoPSafp2myyhTzHC8SpmwEJP0y0ArXfXngKs_eHTKRgyvPR2PJEtsoBzOyvF3h6BbsXbvhAVTT-k8qf0jqU4TscoCDPIbiJ9yo6Y_IFCu14zYHBxVZsED4V_v_Cwd3s935F8vNyqwddSm74izC8maNSYu34f38s" } }, { "name": "ES256", "private_key": "ec-p256-private.pem", "public_key": "ec-p256-public.pem", "protected": { "alg": "ES256" } }, { "name": "ES384", "private_key": "ec-p384-private.pem", "public_key": "ec-p384-public.pem", "protected": { "alg": "ES384" } }, { "name": "ES512", "private_key": "ec-p512-private.pem", "public_key": "ec-p512-public.pem", "protected": { "alg": "ES512" } }, { "name": "ES256K", "private_key": "ec-secp256k1-private.pem", "public_key": "ec-secp256k1-public.pem", "protected": { "alg": "ES256K" } }, { "name": "PS256", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "PS256" } }, { "name": "PS384", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "PS384" } }, { "name": "PS512", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "protected": { "alg": "PS512" } }, { "name": "EdDSA ed448", "private_key": "okp-ed448-private.pem", "public_key": "okp-ed448-public.pem", "protected": { "alg": "EdDSA" } }, { "name": "EdDSA ed25519", "private_key": "okp-ed25519-private.json", "public_key": "okp-ed25519-public.json", "protected": { "alg": "EdDSA" } } ] } joserfc-1.1.0/tests/fixtures/jws_rfc7520.json000066400000000000000000000207361501424510600210330ustar00rootroot00000000000000{ "payload": "It’s a dangerous business, Frodo, going out your door. You step onto the road, and if you don't keep your feet, there’s no knowing where you might be swept off to.", "tests": [ { "name": "RSA v1.5 Signature", "public_key": "RFC7520-RSA-public.json", "private_key": "RFC7520-RSA-private.json", "protected": { "alg": "RS256", "kid": "bilbo.baggins@hobbiton.example" }, "compact": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg", "general_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "signatures": [ { "protected": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" } ] }, "flattened_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "protected": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZopdHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJK3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogoree7vjbU5y18kDquDg" } }, { "name": "RSA-PSS Signature", "public_key": "RFC7520-RSA-public.json", "private_key": "RFC7520-RSA-private.json", "protected": { "alg": "PS384", "kid": "bilbo.baggins@hobbiton.example" }, "compact": "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw", "general_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "signatures": [ { "protected": "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw" } ] }, "flattened_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "protected": "eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN6-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXzg-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aCN_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6oD4ifKo8DYM-X72Eaw" } }, { "name": "ECDSA Signature", "public_key": "RFC7520-EC-public.json", "private_key": "RFC7520-EC-private.json", "protected": { "alg": "ES512", "kid": "bilbo.baggins@hobbiton.example" }, "compact": "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2", "general_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "signatures": [ { "protected": "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2" } ] }, "flattened_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "protected": "eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhbXBsZSJ9", "signature": "AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9Plon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890jl8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2" } }, { "name": "HMAC-SHA2 Integrity Protection", "public_key": "RFC7520-oct-sig.json", "private_key": "RFC7520-oct-sig.json", "protected": { "alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037" }, "compact": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4.s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0", "general_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "signatures": [ { "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" } ] }, "flattened_json": { "payload": "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4", "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" } } ] } joserfc-1.1.0/tests/fixtures/jws_rfc7797.json000066400000000000000000000036511501424510600210500ustar00rootroot00000000000000{ "payload": "$.02", "tests": [ { "name": "4.1 HS256", "protected": { "alg": "HS256" }, "compact": "eyJhbGciOiJIUzI1NiJ9.JC4wMg.5mvfOroL-g7HyqJoozehmsaqmvTYGEq5jTI1gVvoEoQ", "flattened_json": { "protected": "eyJhbGciOiJIUzI1NiJ9", "payload": "JC4wMg", "signature": "5mvfOroL-g7HyqJoozehmsaqmvTYGEq5jTI1gVvoEoQ" } }, { "name": "4.2 HS256 with b64=false", "protected": { "alg": "HS256", "b64": false, "crit": [ "b64" ] }, "compact": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY", "flattened_json": { "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19", "payload": "$.02", "signature": "A5dxf2s96_n5FLueVuW1Z_vh161FwXZC4YLPff6dmDY" } }, { "name": "HS256 with b64=true", "protected": { "alg": "HS256", "b64": true, "crit": [ "b64" ] }, "compact": "eyJhbGciOiJIUzI1NiIsImI2NCI6dHJ1ZSwiY3JpdCI6WyJiNjQiXX0.JC4wMg.6BjugbC8MfrT_yy5WxWVFZrEHVPDtpdsV9u-wbzQDV8", "flattened_json": { "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6dHJ1ZSwiY3JpdCI6WyJiNjQiXX0", "payload": "JC4wMg", "signature": "6BjugbC8MfrT_yy5WxWVFZrEHVPDtpdsV9u-wbzQDV8" } }, { "name": "HS256 with b64=false, safe payload", "payload": "hello", "protected": { "alg": "HS256", "b64": false, "crit": [ "b64" ] }, "compact": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.hello.xsz-SVW1Jtg1IiB5GN-ln0jj2w994q2hTPdPT0bZeQ4", "flattened_json": { "protected": "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19", "payload": "hello", "signature": "xsz-SVW1Jtg1IiB5GN-ln0jj2w994q2hTPdPT0bZeQ4" } } ] } joserfc-1.1.0/tests/fixtures/jwt_use_jws.json000066400000000000000000000070071501424510600214170ustar00rootroot00000000000000{ "payload": {"sub": "1"}, "tests": [ { "name": "HS256", "secret": "secret", "header": { "alg": "HS256" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.FasJnVFa0htLRI3VajVhbweHTHfeKTXV0y6emUBiFGs" }, { "name": "HS384", "secret": "secret", "header": { "alg": "HS384" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJzdWIiOiIxIn0.mfLVeDcIXazg0WuEOk--Vw999c8FIZNcU-e9VyRtQlnxMTICnYqPnsSTzeLY3MN-" }, { "name": "HS512", "secret": "secret", "header": { "alg": "HS512" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIn0.01A1wo_fH3C2SDh2QuAfUOnAoQEP6_ucHynbYYKcRMLvku2CenBgoRcz84U5vHLdA4OeBy-VlhTK_3RNediiMA" }, { "name": "RS256", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "header": { "alg": "RS256" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIn0.dhwB8TIXR93jWcCIcz6ZNwYaImFp1Uy7O13s10ssLh-_XEdABjqdEcgqb-yvGD7xHgKtklBvMr6cFTKjNIMultOwbKgWXqyeW0DTiIr_lVcWeeYxGKNNV0d6Rt6yPF8TBApkVKlYtE84knKu7zkAa2OZf-XmRNDlIGzgJgZq7jaDCBpiv9G_2KZthgJg7Qq1unDIV_RbpnI1187niaGh3wsgtAXKpA_e91Kh_6WKdHOOnpgKWvmH_xc4fIuqiveYuXMQKGj1ditaK64aq4tAYEef_66O1ro9_g2Kh4UAc4TGuxy1nsZglZxZqis_uww43AsY4brMagCNQSjvxHGqQ5n6wcRTEPMrBhWEujOUoy2kH93Wn1B5lf9nk_aXrRb68X3aLirgeDFtw0HA8woe6hvNhw6c30z9UMqyLoP5ct3T6tZ7MFg4OIHukTh4W523MuToJjNaXm0kk8JY1ieOjsErPfCJ9v4L2oSto1vUkry7NJugJx8BT9falUCfweTmb2XuKIuD5PN9p0bdPELHpjXzUngUOTmGJYZXzm0YT4FncXE6AbWwJWIY1h9Zh8Zm8e04bU7Ft0-Chzs_Hsy7VBCaleI-R-0QetMgfc4fl2amHB5q1XSvZ_tYQEi1mqkJYOTtGOkk7yFXScrI6bvRpHqPTmSg0acv82tDBdR8b2Y" }, { "name": "RS384", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "header": { "alg": "RS384" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJzdWIiOiIxIn0.huxdnHc_FvydvHb4_VfUsYlGjSCiGeS_B2DCqcvfLjD1me1iVHNR66NzpLgbaspuNCT-eOgmm9vEQsjx9-fHlu91v_xNY-b0ckKFNj-5KVFY3wrQRJ8ysZAgZCieimSOPL9d96lvmIlI4m21OLjMo91kFHG-mVWmSbv5Q2X058Rl-4IeLP2AeYvvT9hdtC6lPw0dvnvAm-UANxsDWvob2zQyeM45JC4mgGn5WvZUc1UxedwGs2KZrtl-eLXhzlzX5zgbc8ioGN_J91uKN9MwpcOjtyfq3NT6W1GvbVwqvP8Bvs04jxDf8GTU2KH7Kznm0SDE2UFNFKsiKpHBO18WRC9ILZBKe2wYO1D8TVfXHl3WdI6pyvmFLhxB7a6cWiNY_qBTwT_sSUt827ju5lbdhstWiMNDQFmsWNxp6tdHjn1F9m71eANV1DdCIXHI2v_UCzkZ4ioURQlb7qJHcZGPhX9XFfjG-aWHP3Xaof8A_18oH-yyj_bBfbHxUe_Z35hTMcd41hudoyxSVDgj0-TEDQI6XhCji1x77Nnbnsh6A7xZzbIv8dEg2fi-eJqowCvpwQuPIa42BHKphxaxRK1mg4XYWhJ72Ime_SpqxPsyJlYx-EHyS7UERshsteApy_35QxGQTkKj3LAyT42VtuoqeilMsvfXhsrACavo6e4SjaY" }, { "name": "RS512", "private_key": "rsa-openssl-private.pem", "public_key": "rsa-openssl-public.pem", "header": { "alg": "RS512" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiIxIn0.UodywByDhHFbne53THtyjohTbGG0TWakCAEabRf1a3dBm-KswkyenwCEowFkdU90nKCQ-2XexfrwfoD3rdzirErHeIIYXj6bwl0yNvJjwcMbXlJnrgmnntHOGpkl96s-hjqKLQ5xKu5UC_zn80nlrGj7xfyOrYS31jQasM1iR2rv6AqWBhc836_9q0wK7kxJZBnVlH35vFm6r1ADaSQWTRCFMab1E2InfxQXEXPuecVNJCsyNJEZxKzbmaNPE8VtyqTOz-Oa0RYtDzJJsfWIo0V_asEs4-Vt_4XhLxKk26ZLJhv1tVCBZ0BttI5ZRooxwjGkvjtMKRaSjOhhMNODeJMiV4zy5lFRoKpLw1ff-e06pSfzqDHEYtS4fyw9wkCYonkKdQQWsY-Mh7P1XIGyaha-S8jNifW5O82bzHuNGaTZLJQkuqftdLZDKCkkkkSjFcuajfCvSOZkc3dZ6i6H_LvVpobmUSuaDz7WvwZ6MQNtz5zs2jZuI6JzT99oT6pjhroTSLY-pxam5TqU7YIdxj98FQzOj0Vh0mSdqMDA_axsrlytuT_estI-ivSSTX5jnxd5MspzaOdbUrNSu3kQ4AFYPTpWOAoAOus4719z0K25QnCC-eAxFJskbkcD6abNfOioX59rt4wZzbD-RzgCXlrsp5V5g9fbZZtDC1sgzFg" } ] } joserfc-1.1.0/tests/jwe/000077500000000000000000000000001501424510600150715ustar00rootroot00000000000000joserfc-1.1.0/tests/jwe/__init__.py000066400000000000000000000000001501424510600171700ustar00rootroot00000000000000joserfc-1.1.0/tests/jwe/test_chacha20.py000066400000000000000000000046051501424510600200600ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import ( encrypt_compact, decrypt_compact, JWERegistry, ) from joserfc.jwk import OctKey from joserfc.drafts.jwe_chacha20 import register_chaha20_poly1305 register_chaha20_poly1305() chacha_registry = JWERegistry(algorithms=["dir", "C20P", "XC20P"]) class TestChaCha20(TestCase): def run_test_dir(self, enc: str): key = OctKey.generate_key(256) protected = {"alg": "dir", "enc": enc} encrypted_text = encrypt_compact(protected, b"hello", key, registry=chacha_registry) self.assertEqual(encrypted_text.count("."), 4) obj = decrypt_compact(encrypted_text, key, registry=chacha_registry) self.assertEqual(obj.plaintext, b"hello") key2 = OctKey.generate_key(256) self.assertRaises(ValueError, decrypt_compact, encrypted_text, key2, registry=chacha_registry) def test_dir_C20P(self): self.run_test_dir("C20P") def test_dir_XC20P(self): self.run_test_dir("XC20P") def test_xc20p_content_encryption_decryption(self): # https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha-03#appendix-A.3.1 plaintext = bytes.fromhex( "4c616469657320616e642047656e746c656d656e206f662074686520636c6173" "73206f66202739393a204966204920636f756c64206f6666657220796f75206f" "6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" "637265656e20776f756c642062652069742e" ) cek = bytes.fromhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f") iv = bytes.fromhex("404142434445464748494a4b4c4d4e4f5051525354555657") aad = bytes.fromhex("50515253c0c1c2c3c4c5c6c7") XC20P = chacha_registry.get_enc("XC20P") ciphertext, tag = XC20P.encrypt(plaintext, cek, iv, aad) self.assertEqual( ciphertext, bytes.fromhex( "bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb" "731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452" "2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9" "21f9664c97637da9768812f615c68b13b52e" ), ) self.assertEqual(tag, bytes.fromhex("c0875924c1c7987947deafd8780acf49")) result = XC20P.decrypt(ciphertext, tag, cek, iv, aad) self.assertEqual(plaintext, result) joserfc-1.1.0/tests/jwe/test_compact.py000066400000000000000000000212601501424510600201310ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import JWERegistry, encrypt_compact, decrypt_compact, CompactEncryption from joserfc.jwk import RSAKey, ECKey, OctKey, OKPKey, KeySet from joserfc.rfc7518.jwe_encs import JWE_ENC_MODELS from joserfc.errors import ( InvalidKeyLengthError, MissingAlgorithmError, MissingEncryptionError, DecodeError, ExceededSizeError, InvalidHeaderValueError, ) from joserfc.util import json_b64encode from tests.base import load_key class TestJWECompact(TestCase): def run_case(self, alg: str, enc: str, private_key, public_key): protected = {"alg": alg, "enc": enc} payload = b"hello" result = encrypt_compact( protected, payload, public_key, algorithms=[alg, enc], ) self.assertEqual(result.count("."), 4) obj = decrypt_compact( result, private_key, algorithms=[alg, enc], ) self.assertEqual(obj.plaintext, payload) def run_cases(self, names, private_key, public_key): for alg in names: for enc in JWE_ENC_MODELS: self.run_case(alg, enc.name, private_key, public_key) def test_RSA_alg(self): private_key: RSAKey = load_key("rsa-openssl-private.pem") public_key: RSAKey = load_key("rsa-openssl-public.pem") algs = ["RSA1_5", "RSA-OAEP", "RSA-OAEP-256"] self.run_cases(algs, private_key, public_key) protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256"} value = encrypt_compact(protected, "i", private_key) key2 = RSAKey.generate_key() self.assertRaises(DecodeError, decrypt_compact, value, key2) def test_ECDH_ES_with_EC_key(self): algs = ["ECDH-ES", "ECDH-ES+A128KW", "ECDH-ES+A192KW", "ECDH-ES+A256KW"] for size in [256, 384, 512]: private_key: ECKey = load_key(f"ec-p{size}-private.pem") public_key: ECKey = load_key(f"ec-p{size}-public.pem") self.run_cases(algs, private_key, public_key) key1 = ECKey.generate_key("P-256") key2 = ECKey.generate_key("P-256") key3 = ECKey.generate_key("P-521") for alg in ["ECDH-ES", "ECDH-ES+A128KW"]: for enc in ["A128CBC-HS256", "A128GCM"]: protected = {"alg": alg, "enc": enc} value = encrypt_compact(protected, "i", key1) self.assertRaises( DecodeError, decrypt_compact, value, key2, ) self.assertRaises( DecodeError, decrypt_compact, value, key3, ) def test_ECDH_ES_with_OKP_key(self): key1 = OKPKey.generate_key("X25519") key2 = OKPKey.generate_key("X448") for alg in ["ECDH-ES", "ECDH-ES+A128KW"]: for enc in ["A128CBC-HS256", "A128GCM"]: protected = {"alg": alg, "enc": enc} value = encrypt_compact(protected, "i", key1) obj = decrypt_compact(value, key1) self.assertEqual(obj.protected, protected) self.assertRaises( DecodeError, decrypt_compact, value, key2, ) value = encrypt_compact(protected, "i", key2) obj = decrypt_compact(value, key2) self.assertEqual(obj.protected, protected) self.assertRaises( DecodeError, decrypt_compact, value, key1, ) def test_dir_alg(self): key = OctKey.import_key("secret") self.assertRaises(InvalidKeyLengthError, encrypt_compact, {"alg": "dir", "enc": "A128GCM"}, b"j", key) for enc in JWE_ENC_MODELS: key = OctKey.generate_key(enc.cek_size) self.run_case("dir", enc.name, key, key) def test_AESGCM_alg(self): for size in [128, 192, 256]: key = OctKey.generate_key(size) self.run_cases([f"A{size}GCMKW"], key, key) key1 = OctKey.generate_key(128) key2 = OctKey.generate_key(128) protected = {"alg": "A128GCMKW", "enc": "A128CBC-HS256"} algorithms = ["A128GCMKW", "A128CBC-HS256"] value = encrypt_compact(protected, "i", key1, algorithms=algorithms) self.assertRaises( DecodeError, decrypt_compact, value, key2, algorithms=algorithms, ) def test_PBES2HS_alg(self): algs = { "PBES2-HS256+A128KW": 128, "PBES2-HS384+A192KW": 192, "PBES2-HS512+A256KW": 256, } for alg in algs: key = OctKey.generate_key(algs[alg]) self.run_cases([alg], key, key) key1 = OctKey.generate_key(128) key2 = OctKey.generate_key(128) protected = {"alg": "PBES2-HS256+A128KW", "enc": "A128CBC-HS256"} algorithms = ["PBES2-HS256+A128KW", "A128CBC-HS256"] value = encrypt_compact(protected, "i", key1, algorithms=algorithms) self.assertRaises( DecodeError, decrypt_compact, value, key2, algorithms=algorithms, ) def test_PBES2HS_with_header(self): key = OctKey.generate_key(128) protected = { "alg": "PBES2-HS256+A128KW", "enc": "A128CBC-HS256", "p2s": "QoGrcBpns_cLWCQPEVuA-g", "p2c": 1024, } registry = JWERegistry(algorithms=["PBES2-HS256+A128KW", "A128CBC-HS256"]) value1 = encrypt_compact(protected, b"i", key, registry=registry) obj1 = decrypt_compact(value1, key, registry=registry) self.assertIn("p2c", obj1.headers()) self.assertEqual(obj1.headers()["p2c"], 1024) # invalid type protected["p2c"] = "1024" self.assertRaises( InvalidHeaderValueError, encrypt_compact, protected, b"i", key, registry=registry, ) def test_with_zip_header(self): private_key: RSAKey = load_key("rsa-openssl-private.pem") public_key: RSAKey = load_key("rsa-openssl-public.pem") protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256", "zip": "DEF"} plaintext = b"hello" result = encrypt_compact(protected, plaintext, public_key) obj = decrypt_compact(result, private_key) self.assertEqual(obj.plaintext, plaintext) def test_decompress_zip_with_gzip_head(self): key = OctKey.import_key({"k": "pyL42ncDFSYnenl-GiZjRw", "kty": "oct"}) s = ( "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIiwiemlwIjoiREVGIn0.." "YbDfdYa6p-wAEFul.YK7j0MsH-Dko6ifsEg.wES6-QAOEbErZqXiS0JHRw" ) result = decrypt_compact(s, key) self.assertEqual(result.plaintext, b"hello") self.assertEqual(result.headers().get("zip"), "DEF") def test_decompress_zip_exceeds_size(self): key = OctKey.import_key({"k": "pyL42ncDFSYnenl-GiZjRw", "kty": "oct"}) result = encrypt_compact({"alg": "dir", "enc": "A128GCM", "zip": "DEF"}, b"h" * 300000, key) self.assertRaises(ExceededSizeError, decrypt_compact, result, key) def test_invalid_compact_data(self): private_key: RSAKey = load_key("rsa-openssl-private.pem") value = b"a.b.c.d.e.f.g" self.assertRaises(ValueError, decrypt_compact, value, private_key) value = b"a.b.c.d.e" self.assertRaises(DecodeError, decrypt_compact, value, private_key) value = json_b64encode({"enc": "A128CBC-HS256"}) + b".b.c.d.e" self.assertRaises(MissingAlgorithmError, decrypt_compact, value, private_key) value = json_b64encode({"alg": "RSA-OAEP"}) + b".b.c.d.e" self.assertRaises(MissingEncryptionError, decrypt_compact, value, private_key) def test_with_key_set(self): keys = KeySet( [ OctKey.generate_key(), OctKey.generate_key(), RSAKey.generate_key(), ] ) protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256"} value = encrypt_compact(protected, b"foo", keys) obj = decrypt_compact(value, keys) self.assertEqual(obj.plaintext, b"foo") def test_compact_encryption(self): key: RSAKey = load_key("rsa-openssl-private.pem") protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256"} obj = CompactEncryption(protected, b"") self.assertEqual(obj.recipients, []) obj.attach_recipient(key, {"kid": "foo"}) self.assertEqual(obj.protected["kid"], "foo") joserfc-1.1.0/tests/jwe/test_ecdh_1pu.py000066400000000000000000000217121501424510600201750ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import ( JWERegistry, GeneralJSONEncryption, encrypt_compact, decrypt_compact, encrypt_json, decrypt_json, ) from joserfc.jwk import KeySet from joserfc.errors import InvalidEncryptionAlgorithmError from joserfc.rfc7518.jwe_encs import JWE_ENC_MODELS from joserfc.drafts.jwe_ecdh_1pu import JWE_ALG_MODELS, register_ecdh_1pu from tests.base import TestFixture, load_key register_ecdh_1pu() ecdh_registry = JWERegistry(algorithms=[m.name for m in JWE_ALG_MODELS] + [enc.name for enc in JWE_ENC_MODELS]) class TestECDH1PUCompact(TestFixture): def run_test(self, data): alice_key = load_key("ec-p256-alice.json") bob_key = load_key("ec-p256-bob.json") value: str = data["value"] payload = data["payload"].encode("utf-8") obj = decrypt_compact( value.encode("utf-8"), private_key=bob_key, registry=ecdh_registry, sender_key=alice_key, ) self.assertEqual(obj.protected["alg"], data["alg"]) self.assertEqual(obj.protected["enc"], data["enc"]) self.assertEqual(obj.plaintext, payload) def run_compact_case(self, alg: str, enc: str, recipient_key, sender_key): protected = {"alg": alg, "enc": enc} value = encrypt_compact( protected, b"hello", public_key=recipient_key, registry=ecdh_registry, sender_key=sender_key, ) self.assertEqual(value.count("."), 4) obj = decrypt_compact( value, private_key=recipient_key, registry=ecdh_registry, sender_key=sender_key, ) self.assertEqual(obj.plaintext, b"hello") def test_ecdh_1pu_compact_direct_mode(self): alice_key = load_key("ec-p256-alice.json") bob_key = load_key("ec-p256-bob.json") for enc in JWE_ENC_MODELS: self.run_compact_case("ECDH-1PU", enc.name, bob_key, alice_key) alice_key = load_key("okp-x25519-alice.json") bob_key = load_key("okp-x25519-bob.json") for enc in JWE_ENC_MODELS: self.run_compact_case("ECDH-1PU", enc.name, bob_key, alice_key) def test_ecdh_1pu_compact_agreement_mode(self): allowed_alg_values = [ "ECDH-1PU+A128KW", "ECDH-1PU+A192KW", "ECDH-1PU+A256KW", ] allowed_enc_values = [ "A128CBC-HS256", "A192CBC-HS384", "A256CBC-HS512", ] alice_key = load_key("ec-p256-alice.json") bob_key = load_key("ec-p256-bob.json") for alg in allowed_alg_values: for enc in allowed_enc_values: self.run_compact_case(alg, enc, bob_key, alice_key) alice_key = load_key("okp-x25519-alice.json") bob_key = load_key("okp-x25519-bob.json") for alg in allowed_alg_values: for enc in allowed_enc_values: self.run_compact_case(alg, enc, bob_key, alice_key) def test_ecdh_1pu_agreement_mode_with_other_encryption_algorithms(self): alice_key = load_key("ec-p256-alice.json") bob_key = load_key("ec-p256-bob.json") alg_values = [ "ECDH-1PU+A128KW", "ECDH-1PU+A192KW", "ECDH-1PU+A256KW", ] other_enc_values = [ "A128GCM", "A192GCM", "A256GCM", ] for alg in alg_values: for enc in other_enc_values: protected = {"alg": alg, "enc": enc} self.assertRaises( InvalidEncryptionAlgorithmError, encrypt_compact, protected, b"hello", public_key=bob_key, registry=ecdh_registry, sender_key=alice_key, ) def test_load_sender_key_via_skid(self): alice_key = load_key("ec-p256-alice.json", {"kid": "alice"}) bob_key = load_key("ec-p256-bob.json", {"kid": "bob"}) key = KeySet([alice_key, bob_key]) protected = {"alg": "ECDH-1PU+A128KW", "enc": "A128CBC-HS256", "kid": "bob", "skid": "alice"} value = encrypt_compact( protected, b"hello", public_key=key, registry=ecdh_registry, sender_key=key, ) self.assertEqual(value.count("."), 4) obj = decrypt_compact( value, private_key=key, registry=ecdh_registry, sender_key=key, ) self.assertEqual(obj.plaintext, b"hello") def test_sender_key_not_found_via_kid(self): alice_key = load_key("ec-p256-alice.json", {"kid": "alice"}) bob_key = load_key("ec-p256-bob.json", {"kid": "bob"}) protected = {"alg": "ECDH-1PU+A128KW", "enc": "A128CBC-HS256"} value = encrypt_compact( protected, b"hello", public_key=bob_key, registry=ecdh_registry, sender_key=alice_key, ) key_set = KeySet([alice_key, bob_key]) self.assertRaises( ValueError, decrypt_compact, value, private_key=bob_key, registry=ecdh_registry, sender_key=key_set, ) def test_sender_key_not_found_via_alg(self): alice_key = load_key("ec-p256-alice.json", {"kid": "alice"}) protected = {"alg": "ECDH-1PU+A128KW", "enc": "A128CBC-HS256"} bob_key = load_key("RFC7520-RSA-private.json") key_set = KeySet([bob_key]) self.assertRaises( ValueError, encrypt_compact, protected, b"hello", public_key=alice_key, registry=ecdh_registry, sender_key=key_set, ) TestECDH1PUCompact.load_fixture("jwe_compact_ecdh_1pu.json") class TestECDH1PUJSON(TestCase): def test_example_B(self): # https://datatracker.ietf.org/doc/html/draft-madden-jose-ecdh-1pu-04#appendix-B.11 result = { "protected": ( "eyJhbGciOiJFQ0RILTFQVStBMTI4S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiYXB1Ijoi" "UVd4cFkyVSIsImFwdiI6IlFtOWlJR0Z1WkNCRGFHRnliR2xsIiwiZXBrIjp7Imt0eSI6Ik9L" "UCIsImNydiI6IlgyNTUxOSIsIngiOiJrOW9mX2NwQWFqeTBwb1c1Z2FpeFhHczluSGt3ZzFB" "RnFVQUZhMzlkeUJjIn19" ), "unprotected": {"jku": "https://alice.example.com/keys.jwks"}, "recipients": [ { "header": {"kid": "bob-key-2"}, "encrypted_key": ( "pOMVA9_PtoRe7xXW1139NzzN1UhiFoio8lGto9cf0t8PyU-sjNXH8-LIRLycq8CH" "JQbDwvQeU1cSl55cQ0hGezJu2N9IY0QN" ), }, { "header": {"kid": "2021-05-06"}, "encrypted_key": ( "56GVudgRLIMEElQ7DpXsijJVRSWUSDNdbWkdV3g0GUNq6hcT_GkxwnxlPIWrTXCq" "RpVKQC8fe4z3PQ2YH2afvjQ28aiCTWFE" ), }, ], "iv": "AAECAwQFBgcICQoLDA0ODw", "ciphertext": "Az2IWsISEMDJvyc5XRL-3-d-RgNBOGolCsxFFoUXFYw", "tag": "HLb4fTlm8spGmij3RyOs2gJ4DpHM4hhVRwdF_hGb3WQ", } alice_key = load_key("okp-x25519-alice.json") bob_key = load_key("okp-x25519-bob.json", {"kid": "bob-key-2"}) charlie_key = load_key("okp-x25519-charlie.json", {"kid": "2021-05-06"}) protected = { "alg": "ECDH-1PU+A128KW", "enc": "A256CBC-HS512", "apu": "QWxpY2U", "apv": "Qm9iIGFuZCBDaGFybGll", "epk": {"kty": "OKP", "crv": "X25519", "x": "k9of_cpAajy0poW5gaixXGs9nHkwg1AFqUAFa39dyBc"}, } obj = decrypt_json( result, KeySet([bob_key, charlie_key]), registry=ecdh_registry, sender_key=alice_key, ) self.assertEqual(obj.protected, protected) value1 = encrypt_json(obj, KeySet([bob_key, charlie_key]), registry=ecdh_registry, sender_key=alice_key) self.assertEqual(value1["protected"], result["protected"]) def test_with_key_set(self): alice_key = load_key("okp-x25519-alice.json", {"kid": "alice"}) bob_key = load_key("okp-x25519-bob.json", {"kid": "bob"}) charlie_key = load_key("okp-x25519-charlie.json", {"kid": "charlie"}) keys = KeySet([alice_key, bob_key, charlie_key]) obj = GeneralJSONEncryption( {"enc": "A128CBC-HS256"}, plaintext=b"hello", aad=b"world", ) obj.add_recipient({"alg": "ECDH-1PU+A128KW", "kid": "alice", "skid": "charlie"}) obj.add_recipient({"alg": "ECDH-1PU+A256KW", "kid": "bob"}) value = encrypt_json(obj, keys, registry=ecdh_registry, sender_key=keys) obj1 = decrypt_json(value, keys, registry=ecdh_registry, sender_key=keys) self.assertEqual(obj1.plaintext, b"hello") self.assertEqual(obj1.aad, b"world") joserfc-1.1.0/tests/jwe/test_errors.py000066400000000000000000000077541501424510600200330ustar00rootroot00000000000000from unittest import TestCase from joserfc import jwe from joserfc.jwk import OctKey, RSAKey from joserfc.registry import HeaderParameter from joserfc.errors import ( InvalidKeyTypeError, InvalidKeyLengthError, DecodeError, UnsupportedAlgorithmError, UnsupportedHeaderError, ) from tests.base import load_key class TestJWEErrors(TestCase): def test_dir_with_invalid_key_type(self): key1 = load_key("ec-p256-private.pem") protected = {"alg": "dir", "enc": "A128CBC-HS256"} self.assertRaises( InvalidKeyTypeError, jwe.encrypt_compact, protected, b"i", key1, ) protected = {"alg": "A128KW", "enc": "A128CBC-HS256"} self.assertRaises( InvalidKeyTypeError, jwe.encrypt_compact, protected, b"i", key1, ) protected = {"alg": "ECDH-ES+A128KW", "enc": "A128CBC-HS256"} key2 = OctKey.import_key("secret") self.assertRaises( InvalidKeyTypeError, jwe.encrypt_compact, protected, b"i", key2, ) protected = {"alg": "PBES2-HS256+A128KW", "enc": "A128CBC-HS256"} self.assertRaises( InvalidKeyTypeError, jwe.encrypt_compact, protected, b"i", key1, algorithms=["PBES2-HS256+A128KW", "A128CBC-HS256"], ) def test_rsa_with_invalid_key_type(self): key = load_key("ec-p256-private.pem") protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256"} self.assertRaises( InvalidKeyTypeError, jwe.encrypt_compact, protected, b"i", key, ) def test_A128KW_unwrap_error(self): key1 = OctKey.generate_key(128) key2 = OctKey.generate_key(128) protected = {"alg": "A128KW", "enc": "A128CBC-HS256"} value = jwe.encrypt_compact(protected, b"i", key1) self.assertRaises(DecodeError, jwe.decrypt_compact, value, key2) def test_unsupported_algorithm(self): key = OctKey.generate_key(128) protected = {"alg": "INVALID", "enc": "A128CBC-HS256"} self.assertRaises(UnsupportedAlgorithmError, jwe.encrypt_compact, protected, b"i", key) registry = jwe.JWERegistry(algorithms=["A128GCMKW"]) self.assertRaises(UnsupportedAlgorithmError, jwe.encrypt_compact, protected, b"i", key, registry=registry) def test_invalid_key_length(self): protected = {"alg": "dir", "enc": "A128CBC-HS256"} key = OctKey.import_key("secret") self.assertRaises(InvalidKeyLengthError, jwe.encrypt_compact, protected, b"i", key) protected = {"alg": "A128KW", "enc": "A128CBC-HS256"} self.assertRaises(InvalidKeyLengthError, jwe.encrypt_compact, protected, b"i", key) protected = {"alg": "RSA-OAEP", "enc": "A128CBC-HS256"} rsa_key = RSAKey.generate_key(1024) self.assertRaises(InvalidKeyLengthError, jwe.encrypt_compact, protected, b"i", rsa_key) def test_extra_header(self): key = OctKey.generate_key(256) protected = {"alg": "dir", "enc": "A128CBC-HS256", "custom": "hi"} self.assertRaises(UnsupportedHeaderError, jwe.encrypt_compact, protected, b"i", key) registry = jwe.JWERegistry(strict_check_header=False) jwe.encrypt_compact(protected, b"i", key, registry=registry) registry = jwe.JWERegistry(header_registry={"custom": HeaderParameter("Custom", "str")}) jwe.encrypt_compact(protected, b"i", key, registry=registry) def test_strict_check_header_with_more_header_registry(self): key = load_key("ec-p256-private.pem") protected = {"alg": "ECDH-ES", "enc": "A128CBC-HS256", "custom": "hi"} self.assertRaises(UnsupportedHeaderError, jwe.encrypt_compact, protected, b"i", key) registry = jwe.JWERegistry(strict_check_header=False) jwe.encrypt_compact(protected, b"i", key, registry=registry) joserfc-1.1.0/tests/jwe/test_example.py000066400000000000000000000655011501424510600201440ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import decrypt_compact, decrypt_json from joserfc.jwk import RSAKey, OctKey, KeySet from joserfc.util import json_b64encode, urlsafe_b64encode, to_bytes from joserfc.rfc7516.registry import JWERegistry, default_registry as registry from joserfc.rfc7516.models import CompactEncryption, GeneralJSONEncryption from joserfc.rfc7516.message import perform_encrypt from joserfc.rfc7516.compact import represent_compact from joserfc.rfc7516.json import represent_general_json from joserfc.errors import UnsupportedAlgorithmError from tests.base import load_key class TestCompactExamples(TestCase): def test_A1(self): # https://www.rfc-editor.org/rfc/rfc7516#appendix-A.1 # Example JWE using RSAES-OAEP and AES GCM plaintext = b"The true sign of intelligence is not knowledge but imagination." # A.1.1. JOSE Header protected = {"alg": "RSA-OAEP", "enc": "A256GCM"} self.assertEqual(json_b64encode(protected), b"eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ") obj = CompactEncryption(protected, plaintext) # A.1.2. Content Encryption Key (CEK) cek = bytes( [ 177, 161, 244, 128, 84, 143, 225, 115, 63, 180, 3, 255, 107, 154, 212, 246, 138, 7, 110, 91, 112, 46, 34, 105, 47, 130, 203, 46, 122, 234, 64, 252, ] ) # A.1.3. Key Encryption key: RSAKey = load_key("RFC7516-A.1.3.json") obj.attach_recipient(key) enc = registry.get_enc(protected["enc"]) # resulting encrypted key obj.recipient.encrypted_key = bytes( [ 56, 163, 154, 192, 58, 53, 222, 4, 105, 218, 136, 218, 29, 94, 203, 22, 150, 92, 129, 94, 211, 232, 53, 89, 41, 60, 138, 56, 196, 216, 82, 98, 168, 76, 37, 73, 70, 7, 36, 8, 191, 100, 136, 196, 244, 220, 145, 158, 138, 155, 4, 117, 141, 230, 199, 247, 173, 45, 182, 214, 74, 177, 107, 211, 153, 11, 205, 196, 171, 226, 162, 128, 171, 182, 13, 237, 239, 99, 193, 4, 91, 219, 121, 223, 107, 167, 61, 119, 228, 173, 156, 137, 134, 200, 80, 219, 74, 253, 56, 185, 91, 177, 34, 158, 89, 154, 205, 96, 55, 18, 138, 43, 96, 218, 215, 128, 124, 75, 138, 243, 85, 25, 109, 117, 140, 26, 155, 249, 67, 167, 149, 231, 100, 6, 41, 65, 214, 251, 232, 87, 72, 40, 182, 149, 154, 168, 31, 193, 126, 215, 89, 28, 111, 219, 125, 182, 139, 235, 195, 197, 23, 234, 55, 58, 63, 180, 68, 202, 206, 149, 75, 205, 248, 176, 67, 39, 178, 60, 98, 193, 32, 238, 122, 96, 158, 222, 57, 183, 111, 210, 55, 188, 215, 206, 180, 166, 150, 166, 106, 250, 55, 229, 72, 40, 69, 214, 216, 104, 23, 40, 135, 212, 28, 127, 41, 80, 175, 174, 168, 115, 171, 197, 89, 116, 92, 103, 246, 83, 216, 182, 176, 84, 37, 147, 35, 45, 219, 172, 99, 226, 233, 73, 37, 124, 42, 72, 49, 242, 35, 127, 184, 134, 117, 114, 135, 206, ] ) # A.1.4. Initialization Vector iv = bytes([227, 197, 117, 252, 2, 219, 233, 68, 180, 225, 77, 219]) obj.base64_segments["iv"] = urlsafe_b64encode(iv) # A.1.5. Additional Authenticated Data aad = bytes( [ 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 116, 84, 48, 70, 70, 85, 67, 73, 115, 73, 109, 86, 117, 89, 121, 73, 54, 73, 107, 69, 121, 78, 84, 90, 72, 81, 48, 48, 105, 102, 81, ] ) self.assertEqual(json_b64encode(protected), aad) obj.base64_segments["aad"] = aad # A.1.6. Content Encryption ciphertext = bytes( [ 229, 236, 166, 241, 53, 191, 115, 196, 174, 43, 73, 109, 39, 122, 233, 96, 140, 206, 120, 52, 51, 237, 48, 11, 190, 219, 186, 80, 111, 104, 50, 142, 47, 167, 59, 61, 181, 127, 196, 21, 40, 82, 242, 32, 123, 143, 168, 226, 73, 216, 176, 144, 138, 247, 106, 60, 16, 205, 160, 109, 64, 63, 192, ] ) r_ciphertext, r_tag = enc.encrypt(plaintext, cek, iv, aad) self.assertEqual(r_ciphertext, ciphertext) obj.base64_segments["ciphertext"] = urlsafe_b64encode(ciphertext) tag = bytes([92, 80, 104, 49, 133, 25, 161, 215, 173, 101, 219, 211, 136, 91, 210, 145]) self.assertEqual(r_tag, tag) obj.base64_segments["tag"] = urlsafe_b64encode(r_tag) # A.1.7. Complete Representation expected = ( "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV" "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8" "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi" "6UklfCpIMfIjf7iGdXKHzg." "48V1_ALb6US04U3b." "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji" "SdiwkIr3ajwQzaBtQD_A." "XFBoMYUZodetZdvTiFvSkQ" ) self.assertEqual(represent_compact(obj), to_bytes(expected)) jwe_data = decrypt_compact(expected, key) self.assertEqual(jwe_data.plaintext, plaintext) def test_A2(self): # https://www.rfc-editor.org/rfc/rfc7516#appendix-A.2 plaintext = b"Live long and prosper." self.assertEqual( plaintext, bytes( [76, 105, 118, 101, 32, 108, 111, 110, 103, 32, 97, 110, 100, 32, 112, 114, 111, 115, 112, 101, 114, 46] ), ) # A.2.1. JOSE Header protected = {"alg": "RSA1_5", "enc": "A128CBC-HS256"} self.assertEqual(json_b64encode(protected), b"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0") obj = CompactEncryption(protected, plaintext) # A.2.2. Content Encryption Key (CEK) cek = bytes( [ 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, ] ) # A.2.3. Key Encryption key: RSAKey = load_key("RFC7516-A.2.3.json") obj.attach_recipient(key) obj.recipient.encrypted_key = bytes( [ 80, 104, 72, 58, 11, 130, 236, 139, 132, 189, 255, 205, 61, 86, 151, 176, 99, 40, 44, 233, 176, 189, 205, 70, 202, 169, 72, 40, 226, 181, 156, 223, 120, 156, 115, 232, 150, 209, 145, 133, 104, 112, 237, 156, 116, 250, 65, 102, 212, 210, 103, 240, 177, 61, 93, 40, 71, 231, 223, 226, 240, 157, 15, 31, 150, 89, 200, 215, 198, 203, 108, 70, 117, 66, 212, 238, 193, 205, 23, 161, 169, 218, 243, 203, 128, 214, 127, 253, 215, 139, 43, 17, 135, 103, 179, 220, 28, 2, 212, 206, 131, 158, 128, 66, 62, 240, 78, 186, 141, 125, 132, 227, 60, 137, 43, 31, 152, 199, 54, 72, 34, 212, 115, 11, 152, 101, 70, 42, 219, 233, 142, 66, 151, 250, 126, 146, 141, 216, 190, 73, 50, 177, 146, 5, 52, 247, 28, 197, 21, 59, 170, 247, 181, 89, 131, 241, 169, 182, 246, 99, 15, 36, 102, 166, 182, 172, 197, 136, 230, 120, 60, 58, 219, 243, 149, 94, 222, 150, 154, 194, 110, 227, 225, 112, 39, 89, 233, 112, 207, 211, 241, 124, 174, 69, 221, 179, 107, 196, 225, 127, 167, 112, 226, 12, 242, 16, 24, 28, 120, 182, 244, 213, 244, 153, 194, 162, 69, 160, 244, 248, 63, 165, 141, 4, 207, 249, 193, 79, 131, 0, 169, 233, 127, 167, 101, 151, 125, 56, 112, 111, 248, 29, 232, 90, 29, 147, 110, 169, 146, 114, 165, 204, 71, 136, 41, 252, ] ) # A.2.4. Initialization Vector iv = bytes([3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]) obj.base64_segments["iv"] = urlsafe_b64encode(iv) self.assertEqual(obj.base64_segments["iv"], b"AxY8DCtDaGlsbGljb3RoZQ") # A.2.5. Additional Authenticated Data aad = bytes( [ 101, 121, 74, 104, 98, 71, 99, 105, 79, 105, 74, 83, 85, 48, 69, 120, 88, 122, 85, 105, 76, 67, 74, 108, 98, 109, 77, 105, 79, 105, 74, 66, 77, 84, 73, 52, 81, 48, 74, 68, 76, 85, 104, 84, 77, 106, 85, 50, 73, 110, 48, ] ) self.assertEqual(json_b64encode(protected), aad) obj.base64_segments["aad"] = aad # A.2.6. Content Encryption ciphertext = bytes( [ 40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102, ] ) enc = registry.get_enc(protected["enc"]) r_ciphertext, r_tag = enc.encrypt(plaintext, cek, iv, aad) self.assertEqual(r_ciphertext, ciphertext) obj.base64_segments["ciphertext"] = urlsafe_b64encode(ciphertext) self.assertEqual(r_tag, bytes([246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191])) obj.base64_segments["tag"] = urlsafe_b64encode(r_tag) expected = ( "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm" "1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7Pc" "HALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIF" "NPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8" "rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv" "-B3oWh2TbqmScqXMR4gp_A." "AxY8DCtDaGlsbGljb3RoZQ." "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." "9hH0vgRfYgPnAHOd8stkvw" ) self.assertEqual(represent_compact(obj), to_bytes(expected)) # RSA1_5 is not allowed by default self.assertRaises(UnsupportedAlgorithmError, decrypt_compact, expected, key) _registry = JWERegistry(algorithms=["RSA1_5", "A128CBC-HS256"]) jwe_data = decrypt_compact(expected, key, registry=_registry) self.assertEqual(jwe_data.plaintext, plaintext) def test_A3(self): # https://www.rfc-editor.org/rfc/rfc7516#appendix-A.3 plaintext = b"Live long and prosper." protected = {"alg": "A128KW", "enc": "A128CBC-HS256"} key = OctKey.import_key({"kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg"}) expected = ( "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0." "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ." "AxY8DCtDaGlsbGljb3RoZQ." "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY." "U0m_YmjN04DJvceFICbCVQ" ) extract_data = decrypt_compact(expected, key) self.assertEqual(extract_data.protected, protected) self.assertEqual(extract_data.plaintext, plaintext) def test_A4(self): # https://www.rfc-editor.org/rfc/rfc7516#appendix-A.4 # A.4.1. JWE Per-Recipient Unprotected Headers recipient1 = {"alg": "RSA1_5", "kid": "2011-04-29"} recipient2 = {"alg": "A128KW", "kid": "7"} # The algorithm and key used for the first recipient are the same as # that used in Appendix A.2. key1: RSAKey = load_key("RFC7516-A.2.3.json", {"kid": "2011-04-29"}) # The algorithm and key used for the second recipient are the same as # that used in Appendix A.3. key2 = OctKey.import_key({"kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg"}, {"kid": "7"}) keys = KeySet([key1, key2]) # A.4.2. JWE Protected Header protected = {"enc": "A128CBC-HS256"} self.assertEqual(json_b64encode(protected), b"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0") # A.4.3. JWE Shared Unprotected Header shared_header = {"jku": "https://server.example.com/keys.jwks"} # A.4.6. Content Encryption plaintext = b"Live long and prosper." ciphertext = bytes( [ 40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102, ] ) # A.4.7. Complete JWE JSON Serialization Representation expected = { "protected": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", "unprotected": {"jku": "https://server.example.com/keys.jwks"}, "recipients": [ { "header": {"alg": "RSA1_5", "kid": "2011-04-29"}, "encrypted_key": ( "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-" "kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKx" "GHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3" "YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPh" "cCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPg" "wCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A" ), }, { "header": {"alg": "A128KW", "kid": "7"}, "encrypted_key": "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ", }, ], "iv": "AxY8DCtDaGlsbGljb3RoZQ", "ciphertext": "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY", "tag": "Mz-VPPyU4RlcuYv1IwIvzw", } _registry = JWERegistry(algorithms=["RSA1_5", "A128KW", "A128CBC-HS256"]) jwe_data = decrypt_json(expected, keys, registry=_registry) self.assertEqual(jwe_data.plaintext, plaintext) self.assertEqual(jwe_data.protected, protected) self.assertEqual(jwe_data.unprotected, shared_header) self.assertEqual(jwe_data.bytes_segments["ciphertext"], ciphertext) self.assertEqual(jwe_data.recipients[0].header, recipient1) self.assertEqual(jwe_data.recipients[1].header, recipient2) def test_A4_perform(self): recipient1 = {"alg": "RSA1_5", "kid": "2011-04-29"} recipient2 = {"alg": "A128KW", "kid": "7"} protected = {"enc": "A128CBC-HS256"} shared_header = {"jku": "https://server.example.com/keys.jwks"} key1: RSAKey = load_key("RFC7516-A.2.3.json", {"kid": "2011-04-29"}) key2 = OctKey.import_key({"kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg"}, {"kid": "7"}) payload = b"Live long and prosper." obj = GeneralJSONEncryption(protected, payload, shared_header) obj.add_recipient(recipient1, key1) obj.add_recipient(recipient2, key2) _registry = JWERegistry(algorithms=["RSA1_5", "A128KW", "A128CBC-HS256"]) perform_encrypt(obj, _registry) expected = represent_general_json(obj) keys = KeySet([key1, key2]) jwe_data = decrypt_json(expected, keys, registry=_registry) self.assertEqual(jwe_data.plaintext, payload) joserfc-1.1.0/tests/jwe/test_json.py000066400000000000000000000053371501424510600174630ustar00rootroot00000000000000from unittest import TestCase from joserfc import jwe from joserfc.jwe import GeneralJSONEncryption from joserfc.jwk import KeySet, RSAKey, ECKey, OctKey from joserfc.errors import ( DecodeError, ConflictAlgorithmError, InvalidKeyTypeError, ) class TestJWEJSON(TestCase): rsa_key = RSAKey.generate_key() ec_key = ECKey.generate_key() def test_multiple_recipients_with_key(self): obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i") obj.add_recipient({"alg": "RSA-OAEP"}, self.rsa_key) obj.add_recipient({"alg": "ECDH-ES+A128KW"}, self.ec_key) value = jwe.encrypt_json(obj, None) self.assertIn("recipients", value) self.assertEqual(len(value["recipients"]), 2) def test_multiple_recipients_without_key(self): key1 = RSAKey.generate_key(parameters={"kid": "rsa"}) key2 = ECKey.generate_key(parameters={"kid": "ec"}) obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i") obj.add_recipient({"alg": "RSA-OAEP", "kid": "rsa"}) obj.add_recipient({"alg": "ECDH-ES+A128KW", "kid": "ec"}) value = jwe.encrypt_json(obj, KeySet([key1, key2])) self.assertIn("recipients", value) self.assertEqual(len(value["recipients"]), 2) def test_multiple_recipients_with_direct_mode(self): obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i") obj.add_recipient({"alg": "dir"}, OctKey.generate_key()) obj.add_recipient({"alg": "RSA-OAEP"}, self.rsa_key) self.assertRaises( ConflictAlgorithmError, jwe.encrypt_json, obj, None, ) def test_with_aad(self): obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i", aad=b"foo") obj.add_recipient({"alg": "RSA-OAEP"}, self.rsa_key) value = jwe.encrypt_json(obj, None) obj1 = jwe.decrypt_json(value, self.rsa_key) self.assertEqual(obj1.aad, b"foo") def test_decode_multiple_recipients(self): obj = GeneralJSONEncryption({"enc": "A128CBC-HS256"}, b"i") obj.add_recipient({"alg": "RSA-OAEP"}, self.rsa_key) obj.add_recipient({"alg": "ECDH-ES+A128KW"}, self.ec_key) value = jwe.encrypt_json(obj, None) self.assertRaises( InvalidKeyTypeError, jwe.decrypt_json, value, self.rsa_key, ) registry = jwe.JWERegistry(verify_all_recipients=False) obj1 = jwe.decrypt_json(value, self.rsa_key, registry=registry) self.assertEqual(obj1.plaintext, b"i") key3 = OctKey.generate_key() self.assertRaises( DecodeError, jwe.decrypt_json, value, key3, registry=registry, ) joserfc-1.1.0/tests/jwk/000077500000000000000000000000001501424510600150775ustar00rootroot00000000000000joserfc-1.1.0/tests/jwk/__init__.py000066400000000000000000000000001501424510600171760ustar00rootroot00000000000000joserfc-1.1.0/tests/jwk/test_ec_key.py000066400000000000000000000055521501424510600177560ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import ECKey, OctKey from joserfc.errors import InvalidExchangeKeyError from tests.keys import read_key class TestECKey(TestCase): default_key = ECKey.generate_key() def test_exchange_derive_key(self): key1 = ECKey.generate_key("P-256") key2 = ECKey.generate_key("P-384") self.assertRaises(InvalidExchangeKeyError, key1.exchange_derive_key, key2) key3 = ECKey.generate_key("P-256", private=False) self.assertRaises(InvalidExchangeKeyError, key3.exchange_derive_key, key1) def run_import_key(self, name): public_pem = read_key(f"ec-{name}-public.pem") key1 = ECKey.import_key(public_pem) self.assertFalse(key1.is_private) private_pem = read_key(f"ec-{name}-private.pem") key2 = ECKey.import_key(private_pem) self.assertTrue(key2.is_private) # public key match self.assertEqual( key2.as_bytes(private=False), key1.as_bytes(private=False), ) self.assertNotIn("d", key1.dict_value) self.assertIn("d", key2.dict_value) def test_import_p256_key(self): self.run_import_key("p256") def test_import_p384_key(self): self.run_import_key("p384") def test_import_p512_key(self): self.run_import_key("p512") def test_import_secp256k1_key(self): self.run_import_key("secp256k1") def test_generate_key(self): self.assertRaises(ValueError, ECKey.generate_key, "Invalid") key = ECKey.generate_key(private=True) self.assertTrue(key.is_private) self.assertIsNone(key.kid) key = ECKey.generate_key(private=False) self.assertFalse(key.is_private) self.assertIsNone(key.private_key) key = ECKey.generate_key(auto_kid=True) self.assertIsNotNone(key.kid) def test_import_from_der_bytes(self): value1 = self.default_key.as_der() value2 = self.default_key.as_der(private=False) key1 = ECKey.import_key(value1) key2 = ECKey.import_key(value2) self.assertEqual(value1, key1.as_der()) self.assertEqual(value2, key1.as_der(private=False)) self.assertEqual(value2, key2.as_der()) def test_output_with_password(self): key = ECKey.import_key(read_key("ec-p256-private.pem")) pem = key.as_pem(password="secret") self.assertRaises(TypeError, ECKey.import_key, pem) key2 = ECKey.import_key(pem, password="secret") self.assertEqual(key.as_dict(), key2.as_dict()) def test_key_eq(self): key1 = self.default_key key2 = ECKey.import_key(key1.as_dict()) self.assertEqual(key1, key2) key3 = ECKey.generate_key() self.assertNotEqual(key1, key3) def test_key_eq_with_different_types(self): self.assertNotEqual(self.default_key, OctKey.generate_key()) joserfc-1.1.0/tests/jwk/test_jwk_set.py000066400000000000000000000062651501424510600201670ustar00rootroot00000000000000from unittest import TestCase from joserfc.errors import InvalidKeyTypeError, InvalidKeyIdError, MissingKeyError from joserfc.jwk import KeySet, RSAKey, ECKey, OctKey from tests.keys import read_key KeySet.algorithm_keys["RS256"] = ["RSA"] class TestKeySet(TestCase): def test_import_empty_key_set(self): self.assertRaises(MissingKeyError, KeySet.import_key_set, {"keys": []}) def test_generate_and_import_key_set(self): jwks1 = KeySet.generate_key_set("RSA", 2048) self.assertEqual(len(jwks1.keys), 4) for key in jwks1.keys: # we will ensure kid when generating the key set self.assertIsNotNone(key.kid) jwks1_data = jwks1.as_dict() self.assertEqual(list(jwks1_data.keys()), ["keys"]) for d1 in jwks1_data["keys"]: self.assertIn("d", d1) for d1 in jwks1.as_dict(private=False)["keys"]: self.assertNotIn("d", d1) jwks2 = KeySet.import_key_set(jwks1_data) self.assertEqual(len(jwks2.keys), 4) def test_generate_key_set_errors(self): self.assertRaises(InvalidKeyTypeError, KeySet.generate_key_set, "NOT_FOUND", 2048) def test_initialize_key_set(self): keys = [] pem1 = read_key("rsa-openssl-public.pem") keys.append(RSAKey.import_key(pem1)) pem2 = read_key("ec-p256-private.pem") keys.append(ECKey.import_key(pem2)) jwks = KeySet(keys) for d1 in jwks.as_dict(private=False)["keys"]: self.assertNotIn("d", d1) self.assertRaises(ValueError, jwks.as_dict, private=True) def test_random_key(self): key_set = KeySet.generate_key_set("oct", 8, count=1) key1 = key_set.pick_random_key("INVALID") self.assertIsNotNone(key1) key2 = key_set.pick_random_key("RS256") self.assertIsNone(key2) def test_key_set_methods(self): key_set = KeySet.generate_key_set("oct", 8) jwks = key_set.as_dict(custom="hi") self.assertIn("keys", jwks) key = jwks["keys"][0] self.assertEqual(key["custom"], "hi") k1 = key_set.get_by_kid(key["kid"]) self.assertEqual(k1.kid, key["kid"]) self.assertRaises(InvalidKeyIdError, key_set.get_by_kid, "invalid") key_set = KeySet.generate_key_set("oct", 8, count=1) k2 = key_set.get_by_kid() self.assertIsInstance(k2, OctKey) def test_key_set_bool(self): key_set = KeySet([]) self.assertFalse(key_set) key_set = KeySet([OctKey.generate_key()]) self.assertTrue(key_set) def test_key_set_iter(self): key_set = KeySet.generate_key_set("RSA", 2048) for k in key_set: self.assertEqual(k.key_type, "RSA") def test_key_eq_with_same_keys(self): key_set1 = KeySet.generate_key_set("RSA", 2048) key_set2 = KeySet(key_set1.keys) self.assertIsNot(key_set1, key_set2) self.assertEqual(key_set1, key_set2) def test_key_eq_with_new_keys(self): key_set1 = KeySet.generate_key_set("RSA", 2048) key_set2 = KeySet([RSAKey.import_key(k.as_dict()) for k in key_set1]) self.assertIsNot(key_set1, key_set2) self.assertEqual(key_set1, key_set2) joserfc-1.1.0/tests/jwk/test_key_methods.py000066400000000000000000000110011501424510600210140ustar00rootroot00000000000000from unittest import TestCase from joserfc.jws import register_key_set from joserfc.jwk import guess_key, import_key, generate_key from joserfc.jwk import KeySet, OctKey, RSAKey, ECKey, OKPKey from joserfc.errors import ( UnsupportedKeyAlgorithmError, UnsupportedKeyUseError, UnsupportedKeyOperationError, InvalidKeyTypeError, MissingKeyTypeError, InvalidKeyIdError, ) register_key_set() class Guest: def __init__(self): self._headers = {} def headers(self): return self._headers def set_kid(self, kid): self._headers["kid"] = kid class TestKeyMethods(TestCase): def test_guess_str_key(self): self.assertRaises( DeprecationWarning, guess_key, "key", Guest(), ) def test_guess_bytes_key(self): self.assertRaises( DeprecationWarning, guess_key, b"key", Guest(), ) def test_guess_callable_key(self): oct_key = OctKey.generate_key(parameters={"kid": "1"}) rsa_key = RSAKey.generate_key(parameters={"kid": "2"}) def key_func1(obj): return "key" def key_func2(obj): return rsa_key def key_func3(obj): return KeySet([oct_key, rsa_key]) self.assertRaises( DeprecationWarning, guess_key, key_func1, Guest(), ) key2 = guess_key(key_func2, Guest()) self.assertIsInstance(key2, RSAKey) guest = Guest() guest.set_kid("2") key3 = guess_key(key_func3, guest) self.assertIsInstance(key3, RSAKey) def test_guess_key_set(self): key_set = KeySet([OctKey.generate_key(), RSAKey.generate_key()]) guest = Guest() guest._headers["alg"] = "HS256" self.assertRaises(InvalidKeyIdError, guess_key, key_set, guest) key1 = guess_key(key_set, guest, True) self.assertIsInstance(key1, OctKey) guess_key(key_set, guest) guest = Guest() guest._headers["alg"] = "RS256" self.assertRaises(InvalidKeyIdError, guess_key, key_set, guest) key2 = guess_key(key_set, guest, True) self.assertIsInstance(key2, RSAKey) guest = Guest() guest._headers["alg"] = "ES256" self.assertRaises(ValueError, guess_key, key_set, guest, True) def test_invalid_key(self): self.assertRaises(ValueError, guess_key, {}, Guest()) def test_import_key(self): # test bytes key = import_key(b"secret", "oct") self.assertIsInstance(key, OctKey) # test string key = import_key("secret", "oct") self.assertIsInstance(key, OctKey) # test dict data = key.as_dict() key = import_key(data) self.assertIsInstance(key, OctKey) self.assertRaises(InvalidKeyTypeError, import_key, "secret", "invalid") def test_generate_key(self): key = generate_key("oct") self.assertIsInstance(key, OctKey) key = generate_key("RSA") self.assertIsInstance(key, RSAKey) key = generate_key("EC") self.assertIsInstance(key, ECKey) key = generate_key("OKP") self.assertIsInstance(key, OKPKey) self.assertRaises(InvalidKeyTypeError, generate_key, "invalid", 8) def test_check_use(self): key = OctKey.import_key("secret", {"use": "sig"}) key.check_use("sig") self.assertRaises(UnsupportedKeyUseError, key.check_use, "enc") self.assertRaises(UnsupportedKeyUseError, key.check_use, "invalid") def test_check_alg(self): key = OctKey.import_key("secret", {"alg": "HS256"}) key.check_alg("HS256") self.assertRaises(UnsupportedKeyAlgorithmError, key.check_alg, "RS256") def test_alg_property(self): key = OctKey.import_key("secret") self.assertIsNone(key.alg) key = OctKey.import_key("secret", {"alg": "HS256"}) self.assertEqual(key.alg, "HS256") def test_check_ops(self): key = OctKey.import_key("secret", {"key_ops": ["sign", "verify"]}) key.check_key_op("sign") self.assertRaises(UnsupportedKeyOperationError, key.check_key_op, "wrapKey") self.assertRaises(UnsupportedKeyOperationError, key.check_key_op, "invalid") key = RSAKey.generate_key(private=False) self.assertRaises(UnsupportedKeyOperationError, key.check_key_op, "sign") def test_import_without_kty(self): self.assertRaises(MissingKeyTypeError, import_key, {}) joserfc-1.1.0/tests/jwk/test_oct_key.py000066400000000000000000000061571501424510600201560ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import OctKey from tests.keys import read_key class TestOctKey(TestCase): def test_import_key_from_str(self): key = OctKey.import_key("rfc") self.assertEqual(key["k"], "cmZj") self.assertEqual(key.raw_value, b"rfc") self.assertEqual(dict(key), key.as_dict()) def test_import_key_from_bytes(self): key = OctKey.import_key(b"rfc") self.assertEqual(key["k"], "cmZj") self.assertEqual(key.raw_value, b"rfc") self.assertEqual(dict(key), key.as_dict()) def test_import_key_from_dict(self): # https://www.rfc-editor.org/rfc/rfc7517#appendix-A.3 data = { "kty": "oct", "alg": "A128KW", "k": "GawgguFyGrWKav7AX4VKUg", } key = OctKey.import_key(data) self.assertEqual(key.as_dict(), data) # with use and key ops data = { "kty": "oct", "alg": "A128KW", "k": "GawgguFyGrWKav7AX4VKUg", "use": "sig", "key_ops": ["sign", "verify"], } key = OctKey.import_key(data) self.assertEqual(key.as_dict(), data) def test_import_missing_k(self): data = { "kty": "oct", "alg": "A128KW", } self.assertRaises(ValueError, OctKey.import_key, data) def test_invalid_typeof_k(self): data = { "kty": "oct", "alg": "A128KW", "k": 123, } self.assertRaises(ValueError, OctKey.import_key, data) def test_mismatch_use_key_ops(self): data = {"kty": "oct", "alg": "A128KW", "k": "GawgguFyGrWKav7AX4VKUg", "use": "sig", "key_ops": ["wrapKey"]} self.assertRaises(ValueError, OctKey.import_key, data) def test_invalid_use(self): data = { "kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg", "use": "invalid", } self.assertRaises(ValueError, OctKey.import_key, data) def test_invalid_key_ops(self): data = { "kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg", "key_ops": ["invalid"], } self.assertRaises(ValueError, OctKey.import_key, data) def test_import_pem_key(self): public_pem = read_key("ec-p256-public.pem") self.assertWarns(UserWarning, OctKey.import_key, public_pem) def test_generate_key(self): key = OctKey.generate_key() self.assertEqual(len(key.raw_value), 32) self.assertIsNone(key.kid) key = OctKey.generate_key(None) self.assertEqual(len(key.raw_value), 32) self.assertIsNone(key.kid) self.assertRaises(ValueError, OctKey.generate_key, private=False) self.assertRaises(ValueError, OctKey.generate_key, 251) key = OctKey.generate_key(auto_kid=True) self.assertIsNotNone(key.kid) def test_key_eq(self): key1 = OctKey.generate_key() key2 = OctKey.import_key(key1.as_dict()) self.assertIsNot(key1, key2) self.assertEqual(key1, key2) key3 = OctKey.generate_key() self.assertNotEqual(key1, key3) joserfc-1.1.0/tests/jwk/test_okp_key.py000066400000000000000000000077751501424510600201710ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import OKPKey from joserfc.errors import InvalidExchangeKeyError from tests.keys import read_key class TestOKPKey(TestCase): def test_exchange_derive_key(self): key1 = OKPKey.generate_key("Ed448") key2 = OKPKey.generate_key("Ed448") # only X25519 and X448 can exchange self.assertRaises(InvalidExchangeKeyError, key1.exchange_derive_key, key2) key3 = OKPKey.generate_key("X25519", private=False) key4 = OKPKey.generate_key("X25519") # key3 must have private key self.assertRaises(InvalidExchangeKeyError, key3.exchange_derive_key, key4) key5 = OKPKey.generate_key("X448") # curve name doesn't match self.assertRaises(InvalidExchangeKeyError, key4.exchange_derive_key, key5) def test_generate_keys(self): curves = ["Ed25519", "Ed448", "X25519", "X448"] for crv in curves: key = OKPKey.generate_key(crv) self.assertTrue(key.is_private) self.assertEqual(key.curve_name, crv) public_key = OKPKey.generate_key("Ed25519", private=False) self.assertFalse(public_key.is_private) self.assertIsNone(public_key.kid) self.assertRaises(ValueError, OKPKey.generate_key, "invalid") key = OKPKey.generate_key(auto_kid=True) self.assertIsNotNone(key.kid) def test_import_pem_key(self): private_pem = read_key("okp-ed448-private.pem") public_pem = read_key("okp-ed448-public.pem") private_key: OKPKey = OKPKey.import_key(private_pem) public_key: OKPKey = OKPKey.import_key(public_pem) self.assertEqual(private_key.as_pem(), private_pem) self.assertEqual(private_key.as_pem(private=False), public_pem) self.assertEqual(public_key.as_pem(), public_pem) self.assertIn("d", private_key.as_dict()) self.assertNotIn("d", public_key.as_dict()) def test_properties(self): private_pem = read_key("okp-ed448-private.pem") public_pem = read_key("okp-ed448-public.pem") private_key: OKPKey = OKPKey.import_key(private_pem) public_key: OKPKey = OKPKey.import_key(public_pem) self.assertTrue(private_key.is_private) self.assertFalse(public_key.is_private) self.assertEqual(private_key.private_key, private_key.raw_value) self.assertEqual(public_key.public_key, public_key.raw_value) self.assertIsNone(public_key.private_key) def test_import_from_json(self): private_key = OKPKey.import_key(read_key("okp-ed25519-private.json")) public_key = OKPKey.import_key(read_key("okp-ed25519-public.json")) self.assertTrue(private_key.is_private) self.assertFalse(public_key.is_private) def test_all_as_methods(self): private_json = read_key("okp-ed25519-private.json") public_json = read_key("okp-ed25519-public.json") key: OKPKey = OKPKey.import_key(private_json) # as_dict data = key.as_dict() self.assertIn("d", data) self.assertEqual(data, private_json) data = key.as_dict(private=False) self.assertNotIn("d", data) self.assertEqual(data, public_json) # as_pem data = key.as_pem() self.assertIn(b"PRIVATE", data) data = key.as_pem(private=False) self.assertIn(b"PUBLIC", data) # as_der data = key.as_der() self.assertIsInstance(data, bytes) def test_output_with_password(self): key = OKPKey.import_key(read_key("okp-ed25519-private.json")) pem = key.as_pem(password="secret") self.assertRaises(TypeError, OKPKey.import_key, pem) key2 = OKPKey.import_key(pem, password="secret") self.assertEqual(key.as_pem(), key2.as_pem()) def test_key_eq(self): key1 = OKPKey.generate_key() key2 = OKPKey.import_key(key1.as_dict()) self.assertIsNot(key1, key2) self.assertEqual(key1, key2) key3 = OKPKey.generate_key() self.assertNotEqual(key1, key3) joserfc-1.1.0/tests/jwk/test_rsa_key.py000066400000000000000000000137141501424510600201530ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import RSAKey from tests.keys import read_key class TestRSAKey(TestCase): default_key = RSAKey.generate_key() def test_import_key_from_dict(self): # https://www.rfc-editor.org/rfc/rfc7517#appendix-A.1 data = { "kty": "RSA", "n": ( "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86" "zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5" "JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQ" "MicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyr" "dkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF4" "4-csFCur-kEgU8awapJzKnqDKgw" ), "e": "AQAB", "alg": "RS256", "kid": "2011-04-29", } key: RSAKey = RSAKey.import_key(data) self.assertEqual(key.as_dict(), data) self.assertFalse(key.is_private) self.assertIsNone(key.private_key) def test_with_oth(self): data = { "kty": "RSA", "n": ( "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86" "zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5" "JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQ" "MicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyr" "dkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF4" "4-csFCur-kEgU8awapJzKnqDKgw" ), "e": "AQAB", "oth": "invalid information", } self.assertRaises(ValueError, RSAKey.import_key, data) def test_import_only_from_d(self): data = { "kty": "RSA", "n": ( "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmd" "s2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnila" "kGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72Uw" "xrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tu" "EQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevo" "JArm-L5StowjzGy-_bq6Gw" ), "e": "AQAB", "d": ( "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5Knt" "aEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDs" "JzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfC" "s6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2" "Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILS" "B3lOW085-4qE3DzgrTjgyQ" ), } key: RSAKey = RSAKey.import_key(data) self.assertTrue(key.is_private) data["p"] = ( "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68ik918hdDSE" "9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEPkrdoUKW60tgs1aNd_Nnc" "9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM" ) self.assertRaises(ValueError, RSAKey.import_key, data) def test_import_key_from_ssh(self): ssh_public_pem = read_key("ssh-rsa-public.pem") key: RSAKey = RSAKey.import_key(ssh_public_pem) self.assertFalse(key.is_private) ssh_private_pem = read_key("ssh-rsa-private.pem") key: RSAKey = RSAKey.import_key(ssh_private_pem) self.assertTrue(key.is_private) def test_import_key_from_openssl(self): public_pem = read_key("rsa-openssl-public.pem") key: RSAKey = RSAKey.import_key(public_pem) self.assertFalse(key.is_private) private_pem = read_key("rsa-openssl-private.pem") key: RSAKey = RSAKey.import_key(private_pem) self.assertTrue(key.is_private) def test_output_as_methods(self): private_pem = read_key("rsa-openssl-private.pem") key: RSAKey = RSAKey.import_key(private_pem) # as_dict data = key.as_dict() self.assertIn("d", data) data = key.as_dict(private=True) self.assertIn("d", data) data = key.as_dict(private=False) self.assertNotIn("d", data) # as_pem data = key.as_pem() self.assertIn(b"PRIVATE", data) data = key.as_pem(private=True) self.assertIn(b"PRIVATE", data) data = key.as_pem(private=False) self.assertIn(b"PUBLIC", data) # as_der data = key.as_der() self.assertIsInstance(data, bytes) def test_generate_key(self): self.assertRaises(ValueError, RSAKey.generate_key, 8) self.assertRaises(ValueError, RSAKey.generate_key, 601) key = RSAKey.generate_key(private=False) self.assertFalse(key.is_private) self.assertIsNone(key.kid) key = RSAKey.generate_key(auto_kid=True) self.assertIsNotNone(key.kid) def test_import_from_der_bytes(self): value1 = self.default_key.as_der() key1 = RSAKey.import_key(value1) self.assertEqual(value1, key1.as_der()) def test_import_from_certificate(self): firebase_cert = read_key("firebase-cert.pem") key: RSAKey = RSAKey.import_key(firebase_cert) data = key.as_dict() self.assertEqual(data["kty"], "RSA") def test_output_with_password(self): private_pem = read_key("rsa-openssl-private.pem") key: RSAKey = RSAKey.import_key(private_pem) pem = key.as_pem(password="secret") self.assertRaises(TypeError, RSAKey.import_key, pem) key2 = RSAKey.import_key(pem, password="secret") self.assertEqual(key.as_dict(), key2.as_dict()) def test_key_eq(self): key1 = self.default_key key2 = RSAKey.import_key(key1.as_dict()) self.assertIsNot(key1, key2) self.assertEqual(key1, key2) key3 = RSAKey.generate_key() self.assertNotEqual(key1, key3) joserfc-1.1.0/tests/jws/000077500000000000000000000000001501424510600151075ustar00rootroot00000000000000joserfc-1.1.0/tests/jws/__init__.py000066400000000000000000000000001501424510600172060ustar00rootroot00000000000000joserfc-1.1.0/tests/jws/test_compact.py000066400000000000000000000062671501424510600201610ustar00rootroot00000000000000from unittest import TestCase from joserfc.jws import JWSRegistry, serialize_compact, deserialize_compact from joserfc.jwk import OctKey, RSAKey, KeySet from joserfc.errors import ( BadSignatureError, DecodeError, MissingAlgorithmError, UnsupportedAlgorithmError, UnsupportedHeaderError, ) class TestCompact(TestCase): def test_registry_is_none(self): key = OctKey.import_key("secret") value = serialize_compact({"alg": "HS256"}, b"foo", key) expected = "eyJhbGciOiJIUzI1NiJ9.Zm9v.0pehoi-RMZM1jl-4TP_C4Y6BJ-bcmsuzfDyQpkpJkh0" self.assertEqual(value, expected) obj = deserialize_compact(value, key) self.assertEqual(obj.payload, b"foo") def test_bad_signature_error(self): key = OctKey.import_key("incorrect") value = b"eyJhbGciOiJIUzI1NiJ9.Zm9v.0pehoi-RMZM1jl-4TP_C4Y6BJ-bcmsuzfDyQpkpJkh0" self.assertRaises(BadSignatureError, deserialize_compact, value, key) def test_raise_unsupported_algorithm_error(self): key = OctKey.import_key("secret") self.assertRaises(UnsupportedAlgorithmError, serialize_compact, {"alg": "HS512"}, b"foo", key) self.assertRaises(UnsupportedAlgorithmError, serialize_compact, {"alg": "NOT"}, b"foo", key) def test_invalid_length(self): key = OctKey.import_key("secret") self.assertRaises(DecodeError, deserialize_compact, b"a.b.c.d", key) def test_no_invalid_header(self): # invalid base64 value = b"abc.Zm9v.0pehoi" key = OctKey.import_key("secret") self.assertRaises(DecodeError, deserialize_compact, value, key) # no alg value value = b"eyJhIjoiYiJ9.Zm9v.0pehoi" self.assertRaises(MissingAlgorithmError, deserialize_compact, value, key) def test_invalid_payload(self): value = b"eyJhbGciOiJIUzI1NiJ9.a$b.0pehoi" key = OctKey.import_key("secret") self.assertRaises(DecodeError, deserialize_compact, value, key) def test_with_key_set(self): keys = KeySet( [ OctKey.import_key("a"), OctKey.import_key("b"), OctKey.import_key("c"), ] ) value = serialize_compact({"alg": "HS256"}, b"foo", keys) obj = deserialize_compact(value, keys) self.assertEqual(obj.payload, b"foo") keys.keys.append(RSAKey.generate_key(auto_kid=True)) value = serialize_compact({"alg": "RS256"}, b"foo", keys) obj = deserialize_compact(value, keys) self.assertEqual(obj.payload, b"foo") def test_strict_check_header(self): header = {"alg": "HS256", "custom": "hi"} key = OctKey.import_key("secret") self.assertRaises(UnsupportedHeaderError, serialize_compact, header, b"hi", key) registry = JWSRegistry(strict_check_header=False) serialize_compact(header, b"hi", key, registry=registry) def test_non_canonical_signature_encoding(self): text = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.VI29GgHzuh2xfF0bkRYvZIsSuQnbTXSIvuRyt7RDrwo"[:-1] + "p" self.assertRaises( BadSignatureError, deserialize_compact, text, OctKey.import_key("secret") ) joserfc-1.1.0/tests/jws/test_errors.py000066400000000000000000000152741501424510600200450ustar00rootroot00000000000000from unittest import TestCase from joserfc import jws from joserfc.jwk import RSAKey, OctKey from joserfc.registry import HeaderParameter from joserfc.errors import ( BadSignatureError, MissingKeyError, UnsupportedAlgorithmError, UnsupportedKeyUseError, UnsupportedKeyAlgorithmError, UnsupportedKeyOperationError, InvalidKeyTypeError, MissingHeaderError, MissingCritHeaderError, UnsupportedHeaderError, InvalidHeaderValueError, ) from joserfc.util import urlsafe_b64encode from tests.base import load_key class TestJWSErrors(TestCase): key = OctKey.import_key("secret") def test_without_alg(self): self.assertRaises(MissingHeaderError, jws.serialize_compact, {"kid": "123"}, "i", self.key) def test_raise_unsupported_algorithm_error(self): registry = jws.JWSRegistry(algorithms=["HS256", "HS384", "HS512"]) header = {"alg": "HS256"} jws.serialize_compact(header, "i", self.key, registry=registry) # raise error registry = jws.JWSRegistry(algorithms=["HS512"]) self.assertRaises(UnsupportedAlgorithmError, jws.serialize_compact, header, "i", self.key, registry=registry) def test_without_key(self): self.assertRaises(MissingKeyError, jws.serialize_compact, {"alg": "HS256"}, "i", None) header = {"alg": "HS256"} text = jws.serialize_compact(header, "i", self.key) self.assertRaises(MissingKeyError, jws.deserialize_compact, text, None) def test_none_alg(self): header = {"alg": "none"} text = jws.serialize_compact(header, "i", None, algorithms=["none"]) obj = jws.deserialize_compact(text, None, algorithms=["none"]) self.assertEqual(obj.payload, b"i") # none alg has no signature text += "aQ" self.assertRaises(BadSignatureError, jws.deserialize_compact, text, None, algorithms=["none"]) def test_header_invalid_type(self): # kid should be a string header = {"alg": "HS256", "kid": 123} self.assertRaises( InvalidHeaderValueError, jws.serialize_compact, header, "i", self.key, ) # jwk should be a dict header = {"alg": "HS256", "jwk": "dict"} self.assertRaises( InvalidHeaderValueError, jws.serialize_compact, header, "i", self.key, ) # jku should be a URL header = {"alg": "HS256", "jku": "url"} self.assertRaises( InvalidHeaderValueError, jws.serialize_compact, header, "i", self.key, ) # x5c should be a chain of string header = {"alg": "HS256", "x5c": "url"} self.assertRaises( InvalidHeaderValueError, jws.serialize_compact, header, "i", self.key, ) header = {"alg": "HS256", "x5c": [1, 2]} self.assertRaises( InvalidHeaderValueError, jws.serialize_compact, header, "i", self.key, ) def test_crit_header(self): header = {"alg": "HS256", "crit": ["kid"]} # missing kid header self.assertRaises( MissingCritHeaderError, jws.serialize_compact, header, "i", self.key, ) header = {"alg": "HS256", "kid": "1", "crit": ["kid"]} jws.serialize_compact(header, "i", self.key) def test_extra_header(self): header = {"alg": "HS256", "extra": "hi"} self.assertRaises( UnsupportedHeaderError, jws.serialize_compact, header, "i", self.key, ) # bypass extra header registry = jws.JWSRegistry(strict_check_header=False) jws.serialize_compact(header, "i", self.key, registry=registry) # or use a header registry extra_header = {"extra": HeaderParameter("Extra header", "str", False)} registry = jws.JWSRegistry(header_registry=extra_header) jws.serialize_compact(header, "i", self.key, registry=registry) def test_rsa_invalid_signature(self): key1 = RSAKey.generate_key() key2 = RSAKey.generate_key() header = {"alg": "RS256"} text = jws.serialize_compact(header, "i", key1) self.assertRaises(BadSignatureError, jws.deserialize_compact, text, key2) header = {"alg": "PS256"} text = jws.serialize_compact(header, "i", key1, algorithms=["PS256"]) self.assertRaises(BadSignatureError, jws.deserialize_compact, text, key2, algorithms=["PS256"]) def test_ec_incorrect_curve(self): header = {"alg": "ES256"} key = load_key("ec-p512-private.pem") self.assertRaises(ValueError, jws.serialize_compact, header, "i", key) def test_ec_invalid_signature(self): header = {"alg": "ES256"} key1 = load_key("ec-p256-alice.json") key2 = load_key("ec-p256-bob.json") text = jws.serialize_compact(header, "i", key1) self.assertRaises(BadSignatureError, jws.deserialize_compact, text, key2) parts = text.split(".") bad_text = ".".join(parts[:-1]) + "." + urlsafe_b64encode(b"abc").decode("utf-8") self.assertRaises(BadSignatureError, jws.deserialize_compact, bad_text, key1) def test_okp_bad_signature(self): header = {"alg": "EdDSA"} key1 = load_key("okp-ed448-private.pem") key2 = load_key("okp-ed25519-private.json") algorithms = ["EdDSA"] value = jws.serialize_json({"protected": header}, "i", key1, algorithms=algorithms) self.assertRaises( BadSignatureError, jws.deserialize_json, value, key2, algorithms=algorithms, ) class TestJWSWithKeyErrors(TestCase): def test_invalid_key_use(self): key = OctKey.generate_key(parameters={"use": "enc"}) header = {"alg": "HS256"} self.assertRaises(UnsupportedKeyUseError, jws.serialize_compact, header, "i", key) def test_invalid_key_alg(self): key = OctKey.generate_key(parameters={"alg": "HS512"}) header = {"alg": "HS256"} self.assertRaises(UnsupportedKeyAlgorithmError, jws.serialize_compact, header, "i", key) def test_invalid_key_ops(self): key = OctKey.generate_key(parameters={"key_ops": ["verify"]}) header = {"alg": "HS256"} self.assertRaises(UnsupportedKeyOperationError, jws.serialize_compact, header, "i", key) def test_invalid_key_type(self): key = OctKey.generate_key() header = {"alg": "RS256"} self.assertRaises(InvalidKeyTypeError, jws.serialize_compact, header, "i", key) joserfc-1.1.0/tests/jws/test_examples.py000066400000000000000000000030361501424510600203400ustar00rootroot00000000000000from tests.base import TestFixture, load_key from joserfc.jwk import OctKey from joserfc import jws class TestJWSExamples(TestFixture): def run_test(self, data): if "secret" in data: key = OctKey.import_key(data["secret"]) private_key = key public_key = key else: private_key = load_key(data["private_key"]) public_key = load_key(data["public_key"]) protected = data["protected"] payload = data["payload"] algorithms = [protected["alg"]] value1 = jws.serialize_compact(protected, payload, private_key, algorithms=algorithms) obj1 = jws.deserialize_compact(value1, public_key, algorithms=algorithms) self.assertEqual(obj1.protected, protected) if "compact" in data: self.assertEqual(value1, data["compact"]) member = {"protected": protected} value2 = jws.serialize_json(member, payload, private_key, algorithms=algorithms) obj2 = jws.deserialize_json(value2, public_key, algorithms=algorithms) self.assertTrue(obj2.flattened) if "flattened_json" in data: self.assertEqual(value2, data["flattened_json"]) value3 = jws.serialize_json([member], payload, private_key, algorithms=algorithms) obj3 = jws.deserialize_json(value3, public_key, algorithms=algorithms) self.assertFalse(obj3.flattened) if "general_json" in data: self.assertEqual(value3, data["general_json"]) TestJWSExamples.load_fixture("jws_examples.json") joserfc-1.1.0/tests/jws/test_json.py000066400000000000000000000077361501424510600175060ustar00rootroot00000000000000from unittest import TestCase from joserfc.jws import serialize_json, deserialize_json, detach_content from joserfc.jwk import RSAKey, KeySet from joserfc.errors import DecodeError, BadSignatureError from tests.base import load_key class TestJSON(TestCase): def test_serialize_json(self): key: RSAKey = load_key("rsa-openssl-private.pem") member = {"protected": {"alg": "RS256"}} # flattened value = serialize_json(member, b"hello", key) self.assertIn("signature", value) self.assertNotIn("signatures", value) obj = deserialize_json(value, key) self.assertEqual(obj.payload, b"hello") self.assertEqual(obj.headers(), {"alg": "RS256"}) # general value = serialize_json([member], b"hello", key) self.assertNotIn("signature", value) self.assertIn("signatures", value) obj = deserialize_json(value, key) self.assertEqual(obj.payload, b"hello") def test_serialize_with_unprotected_header(self): key: RSAKey = load_key("rsa-openssl-private.pem") member = {"protected": {"alg": "RS256"}, "header": {"kid": "alice"}} value = serialize_json(member, b"hello", key) self.assertIn("header", value) self.assertEqual(value["header"], member["header"]) value = serialize_json([member], b"hello", key) self.assertIn("signatures", value) value = value["signatures"][0] self.assertIn("header", value) self.assertEqual(value["header"], member["header"]) def test_use_key_set(self): key1 = load_key("ec-p256-alice.json", {"kid": "alice"}) key2 = load_key("ec-p256-bob.json", {"kid": "bob"}) keys = KeySet([key1, key2]) member = {"protected": {"alg": "ES256"}} value = serialize_json(member, b"hello", keys) self.assertIn("header", value) self.assertIn("kid", value["header"]) # this will always pick alice key member = {"protected": {"alg": "ES256"}, "header": {"kid": "alice"}} value = serialize_json(member, b"hello", keys) self.assertEqual(value["header"], {"kid": "alice"}) # header can also be an empty value member = {"protected": {"alg": "ES256"}, "header": {}} value = serialize_json(member, b"hello", keys) self.assertIn("kid", value["header"]) def test_detach_content(self): member = {"protected": {"alg": "ES256"}} key = load_key("ec-p256-alice.json") value = serialize_json(member, b"hello", key) self.assertIn("payload", value) new_value = detach_content(value) self.assertNotIn("payload", new_value) # detach again will not raise error detach_content(new_value) def test_invalid_payload(self): member = {"protected": {"alg": "ES256"}} key = load_key("ec-p256-alice.json") value = serialize_json(member, b"hello", key) value["payload"] = "a" self.assertRaises(DecodeError, deserialize_json, value, key) value = serialize_json([member], b"hello", key) value["payload"] = "a" self.assertRaises(DecodeError, deserialize_json, value, key) def test_bad_signature(self): member = {"protected": {"alg": "ES256"}} key1 = load_key("ec-p256-alice.json") key2 = load_key("ec-p256-bob.json") value = serialize_json(member, b"hello", key1) self.assertRaises(BadSignatureError, deserialize_json, value, key2) value = serialize_json([member], b"hello", key1) self.assertRaises(BadSignatureError, deserialize_json, value, key2) def test_with_public_header(self): key: RSAKey = load_key("rsa-openssl-private.pem") member = {"header": {"alg": "RS256", "kid": "abc"}} value = serialize_json(member, b"hello", key) self.assertIn("header", value) obj = deserialize_json(value, key) self.assertEqual(obj.payload, b"hello") self.assertEqual(obj.headers(), {"alg": "RS256", "kid": "abc"}) joserfc-1.1.0/tests/jws/test_rfc7797.py000066400000000000000000000076601501424510600176410ustar00rootroot00000000000000from joserfc.jwk import OctKey from joserfc.rfc7797 import ( JWSRegistry, serialize_compact, deserialize_compact, serialize_json, deserialize_json, ) from joserfc.errors import ( DecodeError, MissingAlgorithmError, BadSignatureError, InvalidHeaderValueError, ) from joserfc.util import to_bytes from joserfc import jws from tests.base import TestFixture default_key = OctKey.import_key( {"kty": "oct", "k": ("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow")} ) class TestRFC7797(TestFixture): def run_test(self, data): protected = data["protected"] payload = data["payload"] value1 = serialize_compact(protected, payload, default_key) self.assertEqual(value1, data["compact"]) obj1 = deserialize_compact(value1, default_key, payload=payload) self.assertEqual(obj1.headers(), protected) self.assertEqual(obj1.payload, to_bytes(payload)) value2 = serialize_json({"protected": protected}, payload, default_key) self.assertEqual(value2, data["flattened_json"]) obj2 = deserialize_json(value2, default_key) self.assertTrue(obj2.flattened) self.assertEqual(obj2.payload, to_bytes(payload)) self.assertEqual(obj2.members[0].protected, protected) def test_b64_without_crit(self): protected = {"alg": "HS256", "b64": False} self.assertRaises(ValueError, serialize_compact, protected, "i", default_key) def test_invalid_b64_value(self): protected = {"alg": "HS256", "b64": "true", "crit": ["b64"]} self.assertRaises(InvalidHeaderValueError, serialize_compact, protected, "i", default_key) def test_compact_invalid_value_length(self): self.assertRaises(ValueError, deserialize_compact, b"a.b.c.d.e", default_key) def test_invalid_header(self): self.assertRaises(DecodeError, deserialize_compact, b"a.b.c", default_key) def test_compact_missing_alg(self): self.assertRaises(MissingAlgorithmError, deserialize_compact, b"e30.a.b", default_key) def test_compact_bad_signature(self): protected = {"alg": "HS256", "b64": False, "crit": ["b64"]} value = serialize_compact(protected, "hello", default_key) key2 = OctKey.import_key("secret") self.assertRaises(BadSignatureError, deserialize_compact, value, key2) def test_compact_use_registry(self): registry = JWSRegistry() protected = {"alg": "HS256", "b64": True, "crit": ["b64"]} value = serialize_compact(protected, "hello", default_key, registry=registry) obj = deserialize_compact(value, default_key, registry=registry) self.assertEqual(obj.protected, protected) protected = {"alg": "HS256"} value = serialize_compact(protected, "hello", default_key, registry=registry) obj = deserialize_compact(value, default_key, registry=registry) self.assertEqual(obj.protected, protected) def test_json_without_protected_header(self): registry = JWSRegistry() header = {"alg": "HS256", "b64": False, "crit": ["b64"]} member = {"header": header} value = serialize_json(member, "hello", default_key, registry=registry) obj = deserialize_json(value, default_key, registry=registry) self.assertTrue(obj.flattened) self.assertEqual(obj.headers(), header) def test_general_json(self): members = [{"protected": {"alg": "HS256"}}] value = jws.serialize_json(members, "hello", default_key) obj = deserialize_json(value, default_key) self.assertFalse(obj.flattened) def test_json_bad_signature(self): member = {"protected": {"alg": "HS256", "b64": False, "crit": ["b64"]}} value = serialize_json(member, "hello", default_key) key2 = OctKey.import_key("secret") self.assertRaises(BadSignatureError, deserialize_json, value, key2) TestRFC7797.load_fixture("jws_rfc7797.json") joserfc-1.1.0/tests/jwt/000077500000000000000000000000001501424510600151105ustar00rootroot00000000000000joserfc-1.1.0/tests/jwt/__init__.py000066400000000000000000000000001501424510600172070ustar00rootroot00000000000000joserfc-1.1.0/tests/jwt/test_claims.py000066400000000000000000000144641501424510600200020ustar00rootroot00000000000000import time import datetime import json import uuid from unittest import TestCase from joserfc import jwt from joserfc.jwk import OctKey from joserfc.errors import ( InsecureClaimError, InvalidClaimError, MissingClaimError, ExpiredTokenError, InvalidTokenError, ) class UUIDEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, uuid.UUID): return str(o) return super().default(o) class TestJWTClaims(TestCase): def test_check_sensitive_data(self): jwt.check_sensitive_data({}) jwt.check_sensitive_data({"card": 123}) for key in ("password", "token", "secret", "secret_key", "api_key"): claims = {key: "123"} self.assertRaises(InsecureClaimError, jwt.check_sensitive_data, claims) claims = {"card": "6011000000000000"} self.assertRaises(InsecureClaimError, jwt.check_sensitive_data, claims) def test_convert_time(self): key = OctKey.import_key("secret") now = datetime.datetime.now() encoded_text = jwt.encode({"alg": "HS256"}, {"iat": now}, key) decoded_data = jwt.decode(encoded_text, key) self.assertIsInstance(decoded_data.claims["iat"], int) def test_essential_claims(self): claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True}) self.assertRaises(MissingClaimError, claims_requests.validate, {"iss": "a"}) claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True}, iss={"essential": True}) self.assertRaises(MissingClaimError, claims_requests.validate, {"sub": "a"}) claims_requests.validate({"sub": "a", "iss": "a", "name": "joserfc"}) def test_essential_empty_value(self): claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True}) self.assertRaises(MissingClaimError, claims_requests.validate, {"sub": None}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": ""}) claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "allow_blank": True}) self.assertRaises(MissingClaimError, claims_requests.validate, {"sub": None}) claims_requests.validate({"sub": ""}) def test_essential_false_value(self): claims_requests = jwt.JWTClaimsRegistry(foo={"essential": True}) claims_requests.validate({"foo": False}) claims_requests.validate({"foo": 0}) def test_option_value(self): claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": "123"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": "a"}) claims_requests.validate({"sub": "123"}) claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": True}) claims_requests.validate({"sub": True}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": False}) claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "value": False}) claims_requests.validate({"sub": False}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": True}) def test_option_values(self): claims_requests = jwt.JWTClaimsRegistry(sub={"essential": True, "values": ["1", "2", True, False]}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"sub": "a"}) claims_requests.validate({"sub": "1"}) claims_requests.validate({"sub": "2"}) claims_requests.validate({"sub": True}) claims_requests.validate({"sub": False}) def test_int_claims(self): now = int(time.time()) claims_requests = jwt.JWTClaimsRegistry(now=now) self.assertRaises(InvalidClaimError, claims_requests.validate, {"exp": "s"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"nbf": "s"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"iat": "s"}) claims_requests.validate({"exp": now + 100, "nbf": now - 100, "iat": now}) self.assertRaises(ExpiredTokenError, claims_requests.validate, {"exp": now - 100}) self.assertRaises(InvalidTokenError, claims_requests.validate, {"nbf": now + 100}) def test_validate_aud(self): claims_requests = jwt.JWTClaimsRegistry(aud={"essential": True, "value": "a"}) # missing aud self.assertRaises(MissingClaimError, claims_requests.validate, {"iss": "a"}) # invalid value self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": "b"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": ["b"]}) # valid value claims_requests.validate({"aud": "a"}) claims_requests.validate({"aud": ["a"]}) # use option values claims_requests = jwt.JWTClaimsRegistry(aud={"essential": True, "values": ["a", "b"]}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": "c"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"aud": ["c"]}) claims_requests.validate({"aud": "a"}) claims_requests.validate({"aud": "b"}) claims_requests.validate({"aud": ["a"]}) claims_requests.validate({"aud": ["b"]}) claims_requests.validate({"aud": ["a", "b"]}) claims_requests.validate({"aud": ["a", "c"]}) # do not validate claims_requests = jwt.JWTClaimsRegistry() claims_requests.validate({"aud": "a"}) claims_requests = jwt.JWTClaimsRegistry(aud={"essential": True}) claims_requests.validate({"aud": "a"}) def test_validate_iat(self): now = int(time.time()) claims_requests = jwt.JWTClaimsRegistry(now=now, leeway=500) claims_requests.validate({"iat": now}) self.assertRaises(InvalidTokenError, claims_requests.validate, {"iat": now + 1000}) def test_validate_nbf(self): now = int(time.time()) claims_requests = jwt.JWTClaimsRegistry(now=now, leeway=500) claims_requests.validate({"nbf": now}) self.assertRaises(InvalidTokenError, claims_requests.validate, {"nbf": now + 1000}) def test_claims_with_uuid_field(self): value = uuid.uuid4() claims = {"uuid": value} key = OctKey.import_key("secret") encoded_text = jwt.encode({"alg": "HS256"}, claims, key, encoder_cls=UUIDEncoder) token = jwt.decode(encoded_text, key) self.assertEqual(token.claims, {"uuid": str(value)}) joserfc-1.1.0/tests/jwt/test_fixtures.py000066400000000000000000000016141501424510600203740ustar00rootroot00000000000000from joserfc import jwt from joserfc.jwk import OctKey from tests.base import TestFixture, load_key class TestJWTFixtures(TestFixture): def run_test(self, data): header = data["header"] algorithms = [header["alg"]] if "secret" in data: key = OctKey.import_key(data["secret"]) private_key = key public_key = key else: private_key = load_key(data["private_key"]) public_key = load_key(data["public_key"]) claims = data["payload"] value = jwt.encode(header, claims, private_key, algorithms=algorithms) if "token" in data: self.assertEqual(value, data["token"]) obj = jwt.decode(value, public_key, algorithms=algorithms) self.assertEqual(obj.header["typ"], "JWT") self.assertEqual(obj.claims, claims) TestJWTFixtures.load_fixture("jwt_use_jws.json") joserfc-1.1.0/tests/jwt/test_jwt.py000066400000000000000000000047461501424510600173400ustar00rootroot00000000000000from unittest import TestCase from joserfc import jws, jwe, jwt from joserfc.jwk import OctKey from joserfc.errors import ( InvalidPayloadError, MissingClaimError, UnsupportedHeaderError, DecodeError, ) class TestJWT(TestCase): def test_invalid_payload(self): key = OctKey.import_key("secret") data = jws.serialize_compact({"alg": "HS256"}, b"hello", key) self.assertRaises(InvalidPayloadError, jwt.decode, data, key) def test_claims_registry(self): key = OctKey.import_key("secret") data = jwt.encode({"alg": "HS256"}, {"sub": "a"}, key) token = jwt.decode(data, key) claims_registry = jwt.JWTClaimsRegistry(iss={"essential": True}) self.assertRaises(MissingClaimError, claims_registry.validate, token.claims) data = jwt.encode({"alg": "HS256"}, {"iss": "a"}, key) obj = jwt.decode(data, key) self.assertEqual(obj.claims["iss"], "a") def test_jwe_format(self): header = {"alg": "A128KW", "enc": "A128GCM"} claims = {"iss": "https://authlib.org"} key = OctKey.generate_key(128) registry = jwe.JWERegistry() result = jwt.encode(header, claims, key, registry=registry) self.assertEqual(result.count("."), 4) token = jwt.decode(result, key, registry=registry) self.assertEqual(token.claims, claims) def test_using_registry(self): key = OctKey.generate_key(128) value1 = jwt.encode({"alg": "HS256"}, {"sub": "a"}, key, registry=jws.JWSRegistry()) jwt.decode(value1, key, registry=jws.JWSRegistry()) value2 = jwt.encode({"alg": "A128KW", "enc": "A128GCM"}, {"sub": "a"}, key, registry=jwe.JWERegistry()) jwt.decode(value2, key, registry=jwe.JWERegistry()) self.assertRaises( KeyError, jwt.encode, {"alg": "HS256"}, {"sub": "a"}, key, registry=jwe.JWERegistry(), ) self.assertRaises( UnsupportedHeaderError, jwt.encode, {"alg": "A128KW", "enc": "A128GCM"}, {"sub": "a"}, key, registry=jws.JWSRegistry(), ) self.assertRaises( ValueError, jwt.decode, value1, key, registry=jwe.JWERegistry(), ) self.assertRaises( DecodeError, jwt.decode, value2, key, registry=jws.JWSRegistry(), ) joserfc-1.1.0/tests/keys/000077500000000000000000000000001501424510600152575ustar00rootroot00000000000000joserfc-1.1.0/tests/keys/RFC7516-A.1.3.json000066400000000000000000000031651501424510600175720ustar00rootroot00000000000000{ "kty": "RSA", "n": "oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUWcJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3Spsk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2asbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMStPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2djYgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw", "e": "AQAB", "d": "kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5NWV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD93Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghkqDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vlt3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSndVTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ", "p": "1r52Xk46c-LsfB5P442p7atdPUrxQSy4mti_tZI3Mgf2EuFVbUoDBvaRQ-SWxkbkmoEzL7JXroSBjSrK3YIQgYdMgyAEPTPjXv_hI2_1eTSPVZfzL0lffNn03IXqWF5MDFuoUYE0hzb2vhrlN_rKrbfDIwUbTrjjgieRbwC6Cl0", "q": "wLb35x7hmQWZsWJmB_vle87ihgZ19S8lBEROLIsZG4ayZVe9Hi9gDVCOBmUDdaDYVTSNx_8Fyw1YYa9XGrGnDew00J28cRUoeBB_jKI1oma0Orv1T9aXIWxKwd4gvxFImOWr3QRL9KEBRzk2RatUBnmDZJTIAfwTs0g68UZHvtc", "dp": "ZK-YwE7diUh0qR1tR7w8WHtolDx3MZ_OTowiFvgfeQ3SiresXjm9gZ5KLhMXvo-uz-KUJWDxS5pFQ_M0evdo1dKiRTjVw_x4NyqyXPM5nULPkcpU827rnpZzAJKpdhWAgqrXGKAECQH0Xt4taznjnd_zVpAmZZq60WPMBMfKcuE", "dq": "Dq0gfgJ1DdFGXiLvQEZnuKEN0UUmsJBxkjydc3j4ZYdBiMRAy86x0vHCjywcMlYYg4yoC4YZa9hNVcsjqA3FeiL19rk8g6Qn29Tt0cj8qqyFpz9vNDBUfCAiJVeESOjJDZPYHdHY8v1b-o-Z2X5tvLx-TCekf7oxyeKDUqKWjis", "qi": "VIMpMYbPf47dT1w_zDUXfPimsSegnMOA1zTaX7aGk_8urY6R8-ZW1FxU7AlWAyLWybqq6t16VFd7hQd0y6flUK4SlOydB61gwanOsXGOAOv82cHq0E3eL4HrtZkUuKvnPrMnsUUFlfUdybVzxyjz9JF_XyaY14ardLSjf4L_FNY" } joserfc-1.1.0/tests/keys/RFC7516-A.2.3.json000066400000000000000000000031651501424510600175730ustar00rootroot00000000000000{ "kty": "RSA", "n": "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", "e": "AQAB", "d": "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", "p": "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEPkrdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", "q": "uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-yBhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", "dp": "w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuvngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcraHawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", "dq": "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", "qi": "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlCtUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZB9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo" } joserfc-1.1.0/tests/keys/RFC7520-EC-108.json000066400000000000000000000005031501424510600176730ustar00rootroot00000000000000{ "kty": "EC", "kid": "peregrin.took@tuckborough.example", "use": "enc", "crv": "P-384", "x": "YU4rRUzdmVqmRtWOs2OpDE_T5fsNIodcG8G5FWPrTPMyxpzsSOGaQLpe2FpxBmu2", "y": "A8-yxCHxkfBz3hKZfI1jUYMjUhsEveZ9THuwFjH2sCNdtksRJU7D5-SkgaFL1ETP", "d": "iTx2pk7wW-GqJkHcEkFQb2EFyYcO7RugmaW3mRrQVAOUiPommT0IdnYK2xDlZh-j" } joserfc-1.1.0/tests/keys/RFC7520-EC-120.json000066400000000000000000000004071501424510600176700ustar00rootroot00000000000000{ "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" } joserfc-1.1.0/tests/keys/RFC7520-EC-private.json000066400000000000000000000006101501424510600210340ustar00rootroot00000000000000{ "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" } joserfc-1.1.0/tests/keys/RFC7520-EC-public.json000066400000000000000000000004451501424510600206460ustar00rootroot00000000000000{ "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" } joserfc-1.1.0/tests/keys/RFC7520-RSA-73.json000066400000000000000000000032601501424510600177550ustar00rootroot00000000000000{ "kty": "RSA", "kid": "frodo.baggins@hobbiton.example", "use": "enc", "n": "maxhbsmBtdQ3CNrKvprUE6n9lYcregDMLYNeTAWcLj8NnPU9XIYegTHVHQjxKDSHP2l-F5jS7sppG1wgdAqZyhnWvXhYNvcM7RfgKxqNx_xAHx6f3yy7s-M9PSNCwPC2lh6UAkR4I00EhV9lrypM9Pi4lBUop9t5fS9W5UNwaAllhrd-osQGPjIeI1deHTwx-ZTHu3C60Pu_LJIl6hKn9wbwaUmA4cR5Bd2pgbaY7ASgsjCUbtYJaNIHSoHXprUdJZKUMAzV0WOKPfA6OPI4oypBadjvMZ4ZAj3BnXaSYsEZhaueTXvZB4eZOAjIyh2e_VOIKVMsnDrJYAVotGlvMQ", "e": "AQAB", "d": "Kn9tgoHfiTVi8uPu5b9TnwyHwG5dK6RE0uFdlpCGnJN7ZEi963R7wybQ1PLAHmpIbNTztfrheoAniRV1NCIqXaW_qS461xiDTp4ntEPnqcKsyO5jMAji7-CL8vhpYYowNFvIesgMoVaPRYMYT9TW63hNM0aWs7USZ_hLg6Oe1mY0vHTI3FucjSM86Nff4oIENt43r2fspgEPGRrdE6fpLc9Oaq-qeP1GFULimrRdndm-P8q8kvN3KHlNAtEgrQAgTTgz80S-3VD0FgWfgnb1PNmiuPUxO8OpI9KDIfu_acc6fg14nsNaJqXe6RESvhGPH2afjHqSy_Fd2vpzj85bQQ", "p": "2DwQmZ43FoTnQ8IkUj3BmKRf5Eh2mizZA5xEJ2MinUE3sdTYKSLtaEoekX9vbBZuWxHdVhM6UnKCJ_2iNk8Z0ayLYHL0_G21aXf9-unynEpUsH7HHTklLpYAzOOx1ZgVljoxAdWNn3hiEFrjZLZGS7lOH-a3QQlDDQoJOJ2VFmU", "q": "te8LY4-W7IyaqH1ExujjMqkTAlTeRbv0VLQnfLY2xINnrWdwiQ93_VF099aP1ESeLja2nw-6iKIe-qT7mtCPozKfVtUYfz5HrJ_XY2kfexJINb9lhZHMv5p1skZpeIS-GPHCC6gRlKo1q-idn_qxyusfWv7WAxlSVfQfk8d6Et0", "dp": "UfYKcL_or492vVc0PzwLSplbg4L3-Z5wL48mwiswbpzOyIgd2xHTHQmjJpFAIZ8q-zf9RmgJXkDrFs9rkdxPtAsL1WYdeCT5c125Fkdg317JVRDo1inX7x2Kdh8ERCreW8_4zXItuTl_KiXZNU5lvMQjWbIw2eTx1lpsflo0rYU", "dq": "iEgcO-QfpepdH8FWd7mUFyrXdnOkXJBCogChY6YKuIHGc_p8Le9MbpFKESzEaLlN1Ehf3B6oGBl5Iz_ayUlZj2IoQZ82znoUrpa9fVYNot87ACfzIG7q9Mv7RiPAderZi03tkVXAdaBau_9vs5rS-7HMtxkVrxSUvJY14TkXlHE", "qi": "kC-lzZOqoFaZCr5l0tOVtREKoVqaAYhQiqIRGL-MzS4sCmRkxm5vZlXYx6RtE1n_AagjqajlkjieGlxTTThHD8Iga6foGBMaAr5uR1hGQpSc7Gl7CF1DZkBJMTQN6EshYzZfxW08mIO8M6Rzuh0beL6fG9mkDcIyPrBXx2bQ_mM" } joserfc-1.1.0/tests/keys/RFC7520-RSA-84.json000066400000000000000000000063071501424510600177640ustar00rootroot00000000000000{ "kty": "RSA", "kid": "samwise.gamgee@hobbiton.example", "use": "enc", "n": "wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE", "e": "AQAB", "alg": "RSA-OAEP", "d": "n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE", "p": "7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ", "q": "zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q", "dp": "19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ", "dq": "S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ", "qi": "FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA" } joserfc-1.1.0/tests/keys/RFC7520-RSA-private.json000066400000000000000000000032601501424510600211760ustar00rootroot00000000000000{ "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_q29pk5xWHoJp009Qf1HvChixRX59ehik", "dq": "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pErAMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJKbi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdKT1cYF8", "qi": "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-NZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDhjJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpPz8aaI4" } joserfc-1.1.0/tests/keys/RFC7520-RSA-public.json000066400000000000000000000006761501424510600210120ustar00rootroot00000000000000{ "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" } joserfc-1.1.0/tests/keys/RFC7520-oct-130.json000066400000000000000000000002111501424510600201600ustar00rootroot00000000000000{ "kty": "oct", "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", "use": "enc", "alg": "A128GCM", "k": "XctOhJAkA-pD9Lh7ZgW_2A" } joserfc-1.1.0/tests/keys/RFC7520-oct-enc.json000066400000000000000000000002361501424510600204310ustar00rootroot00000000000000{ "kty": "oct", "kid": "1e571774-2e08-40da-8308-e8d68773842d", "use": "enc", "alg": "A256GCM", "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8" } joserfc-1.1.0/tests/keys/RFC7520-oct-sig.json000066400000000000000000000002341501424510600204440ustar00rootroot00000000000000{ "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" } joserfc-1.1.0/tests/keys/__init__.py000066400000000000000000000004371501424510600173740ustar00rootroot00000000000000import json from pathlib import Path BASE_PATH = Path(__file__).parent def read_key(filename: str): with open((BASE_PATH / filename).resolve(), "rb") as f: content: bytes = f.read() if filename.endswith(".json"): return json.loads(content) return content joserfc-1.1.0/tests/keys/ec-p256-alice.json000066400000000000000000000003061501424510600203050ustar00rootroot00000000000000{ "kty": "EC", "crv": "P-256", "x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", "y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", "d": "Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg" } joserfc-1.1.0/tests/keys/ec-p256-bob.json000066400000000000000000000003061501424510600177720ustar00rootroot00000000000000{ "kty": "EC", "crv": "P-256", "x": "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", "y": "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", "d": "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" } joserfc-1.1.0/tests/keys/ec-p256-private.pem000066400000000000000000000003431501424510600205130ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIBnRS4Tf1PY6Jb7QOwAM7OWUOMJTBenEWRvGBCGgctBfoAoGCCqGSM49 AwEHoUQDQgAE3r15c+Yd+0GXKysfWtwkqF7k12ylNE9LdfRP4TfkUcJSQXyGQjcx U8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END EC PRIVATE KEY----- joserfc-1.1.0/tests/keys/ec-p256-public.pem000066400000000000000000000002621501424510600203170ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3r15c+Yd+0GXKysfWtwkqF7k12yl NE9LdfRP4TfkUcJSQXyGQjcxU8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/ec-p384-private.pem000066400000000000000000000004401501424510600205130ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDDQy7nBIq/aaPR980Wfqk5HqU7qVjo7fvKEeYY/8XxNE1BKUx5VkrSj G2g5GqgwRtKgBwYFK4EEACKhZANiAARuuP3WJg9DRzKCZ/xsiA66fJ1NoQmK4d7b 1+t9D5f+srq3f9Ttj/NWdn/WaVDf1ectfSQCyInrC8QXBhGqJj0GNIHzvAykCN0H KS5B9yM0oOKMnSGSklLrOXLQKagxLSU= -----END EC PRIVATE KEY----- joserfc-1.1.0/tests/keys/ec-p384-public.pem000066400000000000000000000003271501424510600203230ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbrj91iYPQ0cygmf8bIgOunydTaEJiuHe 29frfQ+X/rK6t3/U7Y/zVnZ/1mlQ39XnLX0kAsiJ6wvEFwYRqiY9BjSB87wMpAjd BykuQfcjNKDijJ0hkpJS6zly0CmoMS0l -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/ec-p512-private.pem000066400000000000000000000005511501424510600205070ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHbAgEBBEFvFujwdb3ZFYnWnUZrFobrksVQfpDGFJ9Zt1ofpUrDBjBd4Z6rNB+x K5OrfJPm2WidZxzsU69J9cCx/ntANMMUWaAHBgUrgQQAI6GBiQOBhgAEANoDiaaU xmbFy1RrRNOSCsOp5lHj3ugLUnoK/MZHTLGL8UNVsw03K4aqqwVvA43CvQiQZE4t gZAEYR/n+mCoXsutAYmlEpwe1e4VZTklnO+WULy8anV5yIjrmdwIDVvJ1IyJuBDK ZO7SyxCnL6S/OW+WjPU9T6ZXcgNRBVaY40zwQ3zh -----END EC PRIVATE KEY----- joserfc-1.1.0/tests/keys/ec-p512-public.pem000066400000000000000000000004141501424510600203110ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA2gOJppTGZsXLVGtE05IKw6nmUePe 6AtSegr8xkdMsYvxQ1WzDTcrhqqrBW8DjcK9CJBkTi2BkARhH+f6YKhey60BiaUS nB7V7hVlOSWc75ZQvLxqdXnIiOuZ3AgNW8nUjIm4EMpk7tLLEKcvpL85b5aM9T1P pldyA1EFVpjjTPBDfOE= -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/ec-secp256k1-private.pem000066400000000000000000000003371501424510600214450ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHQCAQEEIPuQY2DwDanipOspQMvzw30TvLlySoRiF1kogPK5oB5DoAcGBSuBBAAK oUQDQgAErZ9DjXDbhhPcFzBlHP2yG6UzFMw6bC9XchzLAbA5rUAZDBC7IpJKX97G x6Xy/4MTP9/Wevl2SJ4qCb79pHSIkg== -----END EC PRIVATE KEY----- joserfc-1.1.0/tests/keys/ec-secp256k1-public.pem000066400000000000000000000002561501424510600212510ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAErZ9DjXDbhhPcFzBlHP2yG6UzFMw6bC9X chzLAbA5rUAZDBC7IpJKX97Gx6Xy/4MTP9/Wevl2SJ4qCb79pHSIkg== -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/firebase-cert.pem000066400000000000000000000021631501424510600204770ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDHTCCAgWgAwIBAgIJANuivDoSiT/NMA0GCSqGSIb3DQEBBQUAMDExLzAtBgNV BAMMJnNlY3VyZXRva2VuLnN5c3RlbS5nc2VydmljZWFjY291bnQuY29tMB4XDTI0 MTIxMDA3MzI0OVoXDTI0MTIyNjE5NDc0OVowMTEvMC0GA1UEAwwmc2VjdXJldG9r ZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDhywhKx2L+GZJLjmGcOVMCc+x09hrYpCVk3K/LxgJtZZ34 vHeFUjGexazZiSc9LHd03fNmATFcdtqpVW4Qz+xsP5mQXEqbwPQu2qWZhb5VY8Z6 Oh02uFFxRCdegupuzggdqEhc/QlkrKb2Y/undxcIyRcXKWcDaEn+5dqQA2NhjMp5 ir1YsKRanZIJ69wR65Ok8e0YlHHGsP+7uJaW0b1yr0RyilyluEsb7DmpMV/7j7pr cnNnOnL7jhpLW2gjbVzjB1FR5ScNy5gKp72htFkxdGxS58/AQoT06kBu80OI2VMb mijU6JD4b3tIYtHZp9FOiCNhiQS1e/GTDyGT1ZT1AgMBAAGjODA2MAwGA1UdEwEB /wQCMAAwDgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMCMA0G CSqGSIb3DQEBBQUAA4IBAQDOf4MfPzEesqqm50J0gVW1geCiukD6MrH1nPGuUhm8 FUYy/4W6Rx69XnKKRxb1rEQwrqmi8WNiELRrpXugY5ieowuRwlcrPk450bB1IwK5 Jxcqgf4fxwbvqeoADzl0Z0+JxoiYDpH2FMG1HRpdl/YCzB7W0ftv3q1uUqTDLQ+r K6Cm1rHyznsOio9oJknkQ8mPbvE7qSRdCYir806gGurzyhO1RacbR97B8M/vujyc sNxCxxSphHCgCBW1mz4XqgOF4Sd/XYPrO8/2qhoSjqaqta+gmg644afx82NqsW+Z NJrR9cZxaLNEfwzv+fX9s70xT1nSa9U4avINE58o3Dsk -----END CERTIFICATE----- joserfc-1.1.0/tests/keys/okp-ed25519-private.json000066400000000000000000000002771501424510600214150ustar00rootroot00000000000000{"crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "d": "gUF17HCe-pbN7Ej2rDSXl-e7uSj7rQW5u2dNu0KINP0", "kty": "OKP", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y"} joserfc-1.1.0/tests/keys/okp-ed25519-public.json000066400000000000000000000002131501424510600212070ustar00rootroot00000000000000{"crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "kty": "OKP", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y"} joserfc-1.1.0/tests/keys/okp-ed448-private.pem000066400000000000000000000002341501424510600210500ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOaVsPKMXOBfq9aHlDEaMlBY+FR63hwrINHa2X74uHXUr 3/VXE8eMhrr8stXn41CQKqVmFEeL5Uj5Gg== -----END PRIVATE KEY----- joserfc-1.1.0/tests/keys/okp-ed448-public.pem000066400000000000000000000002221501424510600206510ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAcLccm4NG6CZghPOBE80BmngFHqIZEGL7nNJEpHaq4WTBVEDW UoHLNFMSBhwF6bE+7WZSE++fdBwA -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/okp-x25519-alice.json000066400000000000000000000002221501424510600206650ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4", "d": "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU" } joserfc-1.1.0/tests/keys/okp-x25519-bob.json000066400000000000000000000002221501424510600203520ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw", "d": "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg" } joserfc-1.1.0/tests/keys/okp-x25519-charlie.json000066400000000000000000000002221501424510600212170ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "q-LsvU772uV_2sPJhfAIq-3vnKNVefNoIlvyvg1hrnE", "d": "Jcv8gklhMjC0b-lsk5onBbppWAx5ncNtbM63Jr9xBQE" } joserfc-1.1.0/tests/keys/rsa-openssl-private.pem000066400000000000000000000062531501424510600217060ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJJwIBAAKCAgEAm0tWm31IQ3zYU27bk/NZ3wMJOJ+Moska3WqnptWyiVR+p/qC BlV18NUSwshoctTkETi8+HIhOjUPb0WRvQV0YcpsqBVdSuPZ3m4Q+uX/rudAoDKH J6B7vwjfeg4w9aT/YF+Zi61tEy1c15rHKyXAHjSQGzIasOiXK1eSssim6Exx+caR L0/vWV8+0QICmEBVJiJyfDB4O3WXKac+QsI3LM7ZjWqQFdvx3o1v7sDycz0zdpk4 qEK7hEHUsYIsyYHb70iKSkiuo3nqq2HUHklWy322djy/IqEq03KWuePRUZdPTDzl x5qyKpVLpMswYporngvXKpMTCal5HYfAGuYSMuOAVa1oL1gX8W+N4+XNrVCHSCh1 JHjnO2qUT6em/HJ2gERj3kZDDfE6UXVjAw2iUS2lP+GEim3AdUQ1jTO27Vjvuv+r Nk7UjL8iDW1THlvYI9AeQnqtTTBib2b5+k6a8AzSPhMX/F7WP9hf0NUbkYyrJ7zR fERKqLrwpZu83PRWclnB6afPIZcN58uc+4J5516Ryk6PUawbBHj6zfSIDEuwKj71 ki+t0GHaG4RO9QFk75ArsHWrRZNQhELBVep/ohwl4vscRMQFgdwdzZN8ZaaJRPFi h7B+YiwIhuxpAF9fPrETa6UGoBK6MlWKE6EZi5YRKx6rVWvFfMWAV3Tx9uECAwEA AQKCAgBL9hEaI7EKWfIS9aHwf9ORE5IaIWkQY2CBt97j65nWNP9zOUUKxhjXwdHY d2En8lzQ07kTqff42eV/3z7Hf/iKsRJvMWwd6tAyThJ+N6zWqAVjlvOnfYeqTTPL J0/piFjmkjywJxe4jrLgP7R2tZOA8uMeema17D+tkruOOjnyXRpPPELeKrKAO+el It+UC7va2HS5rJfTNdTIKid5Tjjg8RlXZC2wk5J+8x4yYiz2E5StyYr+Ow4wRmc8 oNk5hAzJwejrJxxNmKAiTssMOYF8LjTnJxWzYbRqE54ItZg42dOPDiazeUb3L2n9 5On5AUKen1oTWDeyvTQiLrnYLnvtp2gbZJ51FlTAUuCqZ4qalTHmJ88XOvFON3jm k1bjI5VgcpnytgKJ3wKTigR6W64qn12yGoW2lFxsFBvb4yW8WXdFGyMraM+N8zaw p+EqjXllvSvZ1/1gUTFZbfZCHwIlMTJmu7oULWkNbPJq5qUhR2+CO56Yg/QpE/Id u42IC1EW/MnVebXqICIWJoOU46fcGQnuLrdxejj9roP1sfWf0hLy/JphkbgwrG3k NrFUYAPgnXX4HUGn5xXFadGe/uEoSxG5i+N9h/SblyZvLbsmbPbpigXcabTzgvOO QiOdtwT11CU4/7m5uDDZxD6668h7kEobwKX6hfiWCAI+YSMoAQKCAQEAyjRpIDAk XcVbfsqIKHXDRV5D7t8i6rIW4uT+f5VmUkB9DZ1UYa9koGLEMQ1sb9iJ3Wwob3RE WJa5yvlK8wvmGTy8pZciEJd/fUV2kjO/lcrVZGfza7evcsAn8yJBUdPpTTCOX3af /4+SvfQcgqwb9Y/wvSFZLtBTddedhXznwmjWNm+losIXEfqC3Ps26gSUI7X0dC7E s0xM+PhVNCPDmDd7gn9CxTJHZ7Oq40Apaf39bvRekqwPyXbiukXdwvzwhykH/Epr 4gdR2AeB28khCdmCN67c8SV82oQkVgJcYMSE2nlqD8BO3WdkF3FZrPqPnBH2yq5+ iXKaZhh8g9VaQQKCAQEAxJv9swRMIq0PLvjuw5qsoGkXcOjReQ6WCqaOZcNanLBr E78sWxd5vXoXBE3jFj3PORf6k0ML59wVRwFfKTubV+pEO21EkLEgfzH/iXQUceX1 bkNMLUWNJEswc3Qv7vOyetyAi25xvPq1dNIpwJlSRcv9mJO7Bl0GaNcu9t0W0Pze SP3esylkU9VyQ3uf6jwRrvjABdKgPHuyi3jb731B3BKr+geP5ni8pe168zgW1KSd bAOhn3R+9MrQOq9CMs0aj6k7Qk6lQlkiGUPiSb3S7wJcijkjQ6UJ1IXy2lxjVi3T O3IPaCpMsGM/qeyu+h+EPVn+jcZd9sAhMShpw680oQKCAQBYrEs9tl78UEQjgiXb uGj9zqzz4B6r1ZV7wvhoctgAUg+FHO2YORZjz2xCJqTbF5a952SEG/Ss9MxdWp2n oBw0DRKde32Q0R8zjHbG/rKRufWCpqN1JYRnSiU61lbWz5uMIjMNYjQgGpI7gwXN uDQ6p/jmt+0oPmubTgbiNzhbZSYrkSKOEZeUZstkpTYbwg5E6tJc8PWJu3g15pFW 4CgyZIJhY/WgDMCLlZrnNYfz11KAieG/aH0z2FLtZR4vGEVSwIej9+7/nD4kAobM H5PBggU87g4uIkZyfWiB318rgILSXFRKvAbZyTF3plmxJeA8jRQxJfyPwhY7l5lj JvkBAoIBAFT34V2Tdt/pkM1JEc8BMqeko1fNlnHN5vQ1ZQb/tVJQQAZpsV6wt5E2 iWn3yzNahQr0nPs1l5idmah1JE4qj4kgGlrgbyhlFFlEH16lBwzuR/JeLTbHfyb3 Q7oxtWF8el70mq0njwoQA4m4JgkxecfmT/O3rLUkUNfQX2CazfiFv/8lkDA3rD86 2MXnUIYnbbEDmeEqVMuu3cu+8LYAmQzmGOLWj88X0NeY2XDxhZRijBIZQ6ko7JEY cYNbKK3RzC/YAF84o90Xrk/i8ZHS8q0OhTXLWb0rPyNUvE64bMnaxhZDxfrLhRcZ 3XKvcjNwmXL2SLe2yfcQs4eOIp9KQeECggEAF5uvaBSZbV943xLKeEfJDxiiY5QP AmI7JZq4j/aV/7JQGJ92jSXyO0DMtCcXe+fFm1gVHKMNdJ1HoDiXo+ja7mmRCao7 SHLAPWWBnKC/IccogBPn4Et7ghQL0gVAIwnaiXeX91+sxconODql3fYBxZwtf8yw +XZUai4y5ApB8GulXcSniCbdVMHB/DJMMTByLuKkheDcOtQDB+Ebjeyq5jBBizyx qpiYLafddIk8acr3NqX1Bv3/J2J/1HPM57LBwhlKy5khy/aC03hDt2eOeJXrOqgq udG6Bs/k/8p4kH/FWvFIsiyQfPo/gzgjJIa3F+qgilaZZ5sNs68oXSw9wg== -----END RSA PRIVATE KEY----- joserfc-1.1.0/tests/keys/rsa-openssl-public.pem000066400000000000000000000014401501424510600215030ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAm0tWm31IQ3zYU27bk/NZ 3wMJOJ+Moska3WqnptWyiVR+p/qCBlV18NUSwshoctTkETi8+HIhOjUPb0WRvQV0 YcpsqBVdSuPZ3m4Q+uX/rudAoDKHJ6B7vwjfeg4w9aT/YF+Zi61tEy1c15rHKyXA HjSQGzIasOiXK1eSssim6Exx+caRL0/vWV8+0QICmEBVJiJyfDB4O3WXKac+QsI3 LM7ZjWqQFdvx3o1v7sDycz0zdpk4qEK7hEHUsYIsyYHb70iKSkiuo3nqq2HUHklW y322djy/IqEq03KWuePRUZdPTDzlx5qyKpVLpMswYporngvXKpMTCal5HYfAGuYS MuOAVa1oL1gX8W+N4+XNrVCHSCh1JHjnO2qUT6em/HJ2gERj3kZDDfE6UXVjAw2i US2lP+GEim3AdUQ1jTO27Vjvuv+rNk7UjL8iDW1THlvYI9AeQnqtTTBib2b5+k6a 8AzSPhMX/F7WP9hf0NUbkYyrJ7zRfERKqLrwpZu83PRWclnB6afPIZcN58uc+4J5 516Ryk6PUawbBHj6zfSIDEuwKj71ki+t0GHaG4RO9QFk75ArsHWrRZNQhELBVep/ ohwl4vscRMQFgdwdzZN8ZaaJRPFih7B+YiwIhuxpAF9fPrETa6UGoBK6MlWKE6EZ i5YRKx6rVWvFfMWAV3Tx9uECAwEAAQ== -----END PUBLIC KEY----- joserfc-1.1.0/tests/keys/ssh-rsa-private.pem000066400000000000000000000064651501424510600210250ustar00rootroot00000000000000-----BEGIN OPENSSH PRIVATE KEY----- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn NhAAAAAwEAAQAAAgEA1pvhxBk77vW65T9wkB+OE8gIcfUF0MI5Vl/REp0jK9gskcBskHkF DvYSNesCu7RoBSrBn1RCzPXKgwEjdpF+mlo6I+XSwXWaknOmFpm040dXruiZ+VHOFYKhfF nSiiE4cBl0gD9Jwm+eXmo6R/8wjMHyulpbcxnq4HHTiF8ckdch2jWgErFtvjjjotPfxdJY Fuy44FKwMCaNBBZYqxwjeYQ4+zKPpoW23bdw23JGW8UDbUuaGFfz0+k75ataEU/mgCUTFv +rt1/+RldxXMx0MwOT65/k21TLQUHyYtKCba7x7f3ubeO+RegPYrrPgTYXw3mLGaYPrDtL c7kJqGhBYN3tIf1Asml2fXfU0bniGvdkeD8P/P295OkxfxcQER2X7SYyd01OnAmIf8kuCv 61VWDAcx61pJ6YhYlzyQFzqSLPpWwmuPF7IsZ8oNiqj/b2pHWBifHeYdkLRgwFT/K+oG7p VPefDw8Uar+2KEb8vUY66+nRUYIYpd1LAsDtbbcgq/Hw7lrQ2h6gn9uk4RLRQ3eI5/vaLa w7Mg4KzJrt15jPt7G9Q4BEZpDpq8/DuoovL5GAppeDI8sQTw3wmAYikeEa1Avsu9EVHAXm mbOI1Rnjc1uauIq5hOOSE6ePwdyCbUCWvIx4IFWzC7YfcC34Bs2p636reIzbXMKFSoW2+V 0AAAdIAftzJwH7cycAAAAHc3NoLXJzYQAAAgEA1pvhxBk77vW65T9wkB+OE8gIcfUF0MI5 Vl/REp0jK9gskcBskHkFDvYSNesCu7RoBSrBn1RCzPXKgwEjdpF+mlo6I+XSwXWaknOmFp m040dXruiZ+VHOFYKhfFnSiiE4cBl0gD9Jwm+eXmo6R/8wjMHyulpbcxnq4HHTiF8ckdch 2jWgErFtvjjjotPfxdJYFuy44FKwMCaNBBZYqxwjeYQ4+zKPpoW23bdw23JGW8UDbUuaGF fz0+k75ataEU/mgCUTFv+rt1/+RldxXMx0MwOT65/k21TLQUHyYtKCba7x7f3ubeO+RegP YrrPgTYXw3mLGaYPrDtLc7kJqGhBYN3tIf1Asml2fXfU0bniGvdkeD8P/P295OkxfxcQER 2X7SYyd01OnAmIf8kuCv61VWDAcx61pJ6YhYlzyQFzqSLPpWwmuPF7IsZ8oNiqj/b2pHWB ifHeYdkLRgwFT/K+oG7pVPefDw8Uar+2KEb8vUY66+nRUYIYpd1LAsDtbbcgq/Hw7lrQ2h 6gn9uk4RLRQ3eI5/vaLaw7Mg4KzJrt15jPt7G9Q4BEZpDpq8/DuoovL5GAppeDI8sQTw3w mAYikeEa1Avsu9EVHAXmmbOI1Rnjc1uauIq5hOOSE6ePwdyCbUCWvIx4IFWzC7YfcC34Bs 2p636reIzbXMKFSoW2+V0AAAADAQABAAACAQC3FYlHaFevBsgI51Q6QBFPYumBfo0ViXys 6VVN0ey9bNCpD0YPAo+EMf1bLkDIraHINq+0I4hRnqbDmGcOshUVzT+ofFqOXKwfoLXitg KRmr19JEanYli0FRt7II3y9WBWkgDHoDZmwB6VYX6TCWv7yUIwJQG7cjLkg3b48ltHOAdT R0hmaiO7koDw2lwfQdGQzSbziNdyXJEVGZNPdtP0yQ5rjrjqUUyuXd7T9+t6QtsnlMXDWt VSxbkpuENAXa/BRt/AUSHHcQdWLycxCeNf2f+JloEBdJdp9r63++r1c7hFVsrfyNj8fnsR uVlpXCJtyvUWTos0XemCsitBFqAeWU7BKmbDf6B3+IpMSp0y5EpIrXMaVeGoH/QWxvoBj3 jLaur8AKMkd4aJJpUnijRHmHOg6bvBz+zXBWvNVe/90s8WuOqns9gyFnYBKvhdUEINwsDA nBtTD7gNfSoJA5qCz9fVTd8nCnejAF9j/M5v2K5wc3gWT/wQsKkfI/q01Ijg5GirsRDAiP Dt4/o3kfCm6kb9/Agb3TLVJ60EHi6U54U+JUKQEyKI75aAJ5QS9aHadjb5udSEUZChnfJs VtJVTayjRfSpXlgVXHFqeDstnGFmzKk90OUXVLG+pknsin9lisQCL+1QDNCwp1a0kDR5k3 0DIGG2koNxSEpKR8i4KQAAAQAcU6MgQ+nnN4wi92TPqOlSUsT4hUDcFQn2STGxhYgtYI7o xzd9R9gZF22yB+eWG9x3fVYz0W5MxCTsVrknvaQS8gCphke1t5jf2qMAqgkRFzmKWRQ2YI MkW1Bdh1RVJ7O+LWkgmV2StqEXAt029ClFOcgtKqS4f1mx5hAyUtMsEtB4mEsI6p/tacZQ VNfPH28Uft91GtM2mefzohK0tDDsbLAJdqd2+WhWnth1Ahzp3vibBsQy9M6watnOYev6S5 qyNrgI2b2mRtYkEEhPfWwhV/HpmSD0SFd7M63owE98rckhRBRgsy4uXh8UndoLEfrju/rm jG26zZgl5Kp8k8iFAAABAQDyXgfuRkZP6F9rECAeui0pYGxUW6fAkqjzjYOWqIw52Stro2 9tMrEK79omq3N6tDt9GjaXt4r6LF2hVCIgpxTR71uTc7736/ZQ77kCD7njYyJh4/LMtel7 lDa+DE8FpIGL1c1zv8B3VovC/C+wsGSm3rkLuCEsUfrjXP5w4rSZtZRLfQlr9m+G2CDNLS AaHfyqx76yhJqJDb8tXovpMGCXEIG3+MPWGQO90+XUTIt9TN8pZM8PTp44l7AKxTjJ7L7V hPrehSML0nKVfayJrPw+cjrLa0WP56WLkMvBfK/LPSWCqJ985PbYyZrnDtjXy+eYgA0ijX nIB9eriWhGFITrAAABAQDiriTQOL/KAznuGS/ZJVuf7fQ8jksbLQ55w9tKHVvNTrWybyF2 otKBDO1CnqjFfa6Danx1kmG2vmn+6b82A3GII05FAjR4nKdn4LL579g+dHIOa7+ngd7HVK iND7atn7f2okgSowS9F6LSaFIJkmO1C0JFYlvcVfG3GUArsjh5CDPNyVfX7gPychTl03Ne liIcPiIs7VNNmfSJkwr4cUmFS3hD2V8xf1CcDPSvgJ9u9TuzemmXeEAhQHFzZ4uNWjmljU p0LxH1EMEi8UBFJmP2eIX2d1MjZJNkTs/Xv9oV1NWOLOlr7BBb5TsJEmLxJCfjo99E6szL gIaVbKzN/AjXAAAADGxlcHR1cmVAQVNVUwECAwQFBg== -----END OPENSSH PRIVATE KEY----- joserfc-1.1.0/tests/keys/ssh-rsa-public.pem000066400000000000000000000013421501424510600206160ustar00rootroot00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDWm+HEGTvu9brlP3CQH44TyAhx9QXQwjlWX9ESnSMr2CyRwGyQeQUO9hI16wK7tGgFKsGfVELM9cqDASN2kX6aWjoj5dLBdZqSc6YWmbTjR1eu6Jn5Uc4VgqF8WdKKIThwGXSAP0nCb55eajpH/zCMwfK6WltzGergcdOIXxyR1yHaNaASsW2+OOOi09/F0lgW7LjgUrAwJo0EFlirHCN5hDj7Mo+mhbbdt3DbckZbxQNtS5oYV/PT6Tvlq1oRT+aAJRMW/6u3X/5GV3FczHQzA5Prn+TbVMtBQfJi0oJtrvHt/e5t475F6A9ius+BNhfDeYsZpg+sO0tzuQmoaEFg3e0h/UCyaXZ9d9TRueIa92R4Pw/8/b3k6TF/FxARHZftJjJ3TU6cCYh/yS4K/rVVYMBzHrWknpiFiXPJAXOpIs+lbCa48Xsixnyg2KqP9vakdYGJ8d5h2QtGDAVP8r6gbulU958PDxRqv7YoRvy9Rjrr6dFRghil3UsCwO1ttyCr8fDuWtDaHqCf26ThEtFDd4jn+9otrDsyDgrMmu3XmM+3sb1DgERmkOmrz8O6ii8vkYCml4MjyxBPDfCYBiKR4RrUC+y70RUcBeaZs4jVGeNzW5q4irmE45ITp4/B3IJtQJa8jHggVbMLth9wLfgGzanrfqt4jNtcwoVKhbb5XQ== lepture@ASUS joserfc-1.1.0/tests/rfc7520/000077500000000000000000000000001501424510600153745ustar00rootroot00000000000000joserfc-1.1.0/tests/rfc7520/__init__.py000066400000000000000000000000001501424510600174730ustar00rootroot00000000000000joserfc-1.1.0/tests/rfc7520/test_jwe.py000066400000000000000000000156171501424510600176040ustar00rootroot00000000000000import json from tests.base import TestFixture, load_key from joserfc.jwk import ECKey, OctKey from joserfc.jwe import ( GeneralJSONEncryption, FlattenedJSONEncryption, encrypt_compact, decrypt_compact, encrypt_json, decrypt_json, ) payload = ( b"You can trust us to stick with you through thick and " b"thin\xe2\x80\x93to the bitter end. And you can trust us to " b"keep any secret of yours\xe2\x80\x93closer than you keep it " b"yourself. But you cannot trust us to let you face trouble " b"alone, and go off without a word. We are your friends, Frodo." ) class TestJWERFC7520(TestFixture): def run_test(self, data): protected = data["protected"] key = load_key(data["key"]) algorithms = [protected["alg"], protected["enc"]] value1 = encrypt_compact(protected, payload, key, algorithms=algorithms) obj1 = decrypt_compact(value1, key, algorithms=algorithms) compact_obj = decrypt_compact(data["compact"], key, algorithms=algorithms) self.assertEqual(obj1.protected, compact_obj.protected) self.assertEqual(obj1.plaintext, compact_obj.plaintext) enc1 = GeneralJSONEncryption(protected, payload) enc1.add_recipient(None, key) value2 = encrypt_json(enc1, None, algorithms=algorithms) obj2 = decrypt_json(value2, key, algorithms=algorithms) self.assertEqual(obj2.protected, protected) self.assertFalse(obj2.flattened) self.assertEqual(obj2.plaintext, payload) if "general_json" in data: general_obj = decrypt_json(data["general_json"], key, algorithms=algorithms) self.assertEqual(general_obj.protected, protected) self.assertEqual(general_obj.plaintext, payload) self.assertFalse(general_obj.flattened) enc2 = FlattenedJSONEncryption(protected, payload) enc2.add_recipient(None, key) value3 = encrypt_json(enc2, None, algorithms=algorithms) obj3 = decrypt_json(value3, key, algorithms=algorithms) self.assertEqual(obj3.protected, protected) self.assertEqual(obj3.plaintext, payload) self.assertTrue(obj3.flattened) if "flattened_json" in data: flattened_obj = decrypt_json(data["flattened_json"], key, algorithms=algorithms) self.assertEqual(flattened_obj.protected, protected) self.assertEqual(flattened_obj.plaintext, payload) self.assertTrue(flattened_obj.flattened) def run_test_agreement(self, data): protected = data["protected"] ephemeral_key = ECKey.import_key(data["epk"]) expected_header = {**protected, "epk": ephemeral_key.as_dict(private=False)} key = load_key(data["key"]) algorithms = [protected["alg"], protected["enc"]] compact_obj = decrypt_compact(data["compact"], key, algorithms=algorithms) self.assertEqual(compact_obj.plaintext, payload) self.assertEqual(compact_obj.protected, expected_header) general_obj = decrypt_json(data["general_json"], key, algorithms=algorithms) self.assertEqual(general_obj.plaintext, payload) self.assertEqual(general_obj.protected, expected_header) if "flattened_json" in data: flattened_obj = decrypt_json(data["flattened_json"], key, algorithms=algorithms) self.assertEqual(flattened_obj.plaintext, payload) self.assertEqual(flattened_obj.protected, expected_header) enc3 = GeneralJSONEncryption(protected, payload) enc3.add_recipient(None, key) enc3.recipients[0].ephemeral_key = ephemeral_key value2 = encrypt_json(enc3, None, algorithms=algorithms) obj2 = decrypt_json(value2, key, algorithms=algorithms) recipient = obj2.recipients[0] self.assertEqual(recipient.headers(), expected_header) enc4 = FlattenedJSONEncryption(protected, payload) enc4.add_recipient(None, key) enc4.recipients[0].ephemeral_key = ephemeral_key value3 = encrypt_json(enc4, None, algorithms=algorithms) obj3 = decrypt_json(value3, key, algorithms=algorithms) recipient = obj3.recipients[0] self.assertEqual(recipient.headers(), expected_header) def test_5_3(self): # Key Wrap Using PBES2-AES-KeyWrap with AES-CBC-HMAC-SHA2 plaintext = { "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", }, ] } password = OctKey.import_key(b"entrap_o\xe2\x80\x93peter_long\xe2\x80\x93credit_tun") protected = { "alg": "PBES2-HS512+A256KW", "p2s": "8Q1SzinasR3xchYz6ZZcHA", "p2c": 8192, "cty": "jwk-set+json", "enc": "A128CBC-HS256", } compact_data = """ eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJwMnMiOiI4UTFTemluYXNSM3 hjaFl6NlpaY0hBIiwicDJjIjo4MTkyLCJjdHkiOiJqd2stc2V0K2pzb24iLCJl bmMiOiJBMTI4Q0JDLUhTMjU2In0 . d3qNhUWfqheyPp4H8sjOWsDYajoej4c5Je6rlUtFPWdgtURtmeDV1g . VBiCzVHNoLiR3F4V82uoTQ . 23i-Tb1AV4n0WKVSSgcQrdg6GRqsUKxjruHXYsTHAJLZ2nsnGIX86vMXqIi6IR sfywCRFzLxEcZBRnTvG3nhzPk0GDD7FMyXhUHpDjEYCNA_XOmzg8yZR9oyjo6l TF6si4q9FZ2EhzgFQCLO_6h5EVg3vR75_hkBsnuoqoM3dwejXBtIodN84PeqMb 6asmas_dpSsz7H10fC5ni9xIz424givB1YLldF6exVmL93R3fOoOJbmk2GBQZL _SEGllv2cQsBgeprARsaQ7Bq99tT80coH8ItBjgV08AtzXFFsx9qKvC982KLKd PQMTlVJKkqtV4Ru5LEVpBZXBnZrtViSOgyg6AiuwaS-rCrcD_ePOGSuxvgtrok AKYPqmXUeRdjFJwafkYEkiuDCV9vWGAi1DH2xTafhJwcmywIyzi4BqRpmdn_N- zl5tuJYyuvKhjKv6ihbsV_k1hJGPGAxJ6wUpmwC4PTQ2izEm0TuSE8oMKdTw8V 3kobXZ77ulMwDs4p . 0HlwodAhOCILG5SQ2LQ9dg """.replace(" ", "").replace("\n", "") algorithms = [protected["alg"], protected["enc"]] value1 = encrypt_compact(protected, json.dumps(plaintext), password, algorithms=algorithms) obj1 = decrypt_compact(value1, password, algorithms=algorithms) self.assertEqual(json.loads(obj1.plaintext), plaintext) compact_obj = decrypt_compact(compact_data, password, algorithms=algorithms) self.assertEqual(json.loads(compact_obj.plaintext), plaintext) TestJWERFC7520.load_fixture("jwe_rfc7520.json") joserfc-1.1.0/tests/rfc7520/test_jws.py000066400000000000000000000205041501424510600176110ustar00rootroot00000000000000from tests.base import TestFixture, load_key from joserfc import jws from joserfc.util import urlsafe_b64encode # https://datatracker.ietf.org/doc/html/rfc7520#section-4 payload = ( b"It\xe2\x80\x99s a dangerous business, Frodo, going out your door. " b"You step onto the road, and if you don't keep your feet, " b"there\xe2\x80\x99s no knowing where you might be swept off to." ) b64_payload = urlsafe_b64encode(payload).decode("utf-8") class TestJWSRFC7520(TestFixture): def run_test(self, data): private_key = load_key(data["private_key"]) public_key = load_key(data["public_key"]) protected = data["protected"] algorithms = [protected["alg"]] obj1 = jws.deserialize_compact(data["compact"], public_key, algorithms=algorithms) self.assertEqual(obj1.payload, payload) obj2 = jws.deserialize_json(data["general_json"], public_key, algorithms=algorithms) self.assertEqual(obj2.payload, payload) self.assertFalse(obj2.flattened) obj3 = jws.deserialize_json(data["flattened_json"], public_key, algorithms=algorithms) self.assertEqual(obj3.payload, payload) self.assertTrue(obj3.flattened) # try serialize and deserialize pair value4 = jws.serialize_compact(protected, payload, private_key, algorithms=algorithms) obj4 = jws.deserialize_compact(value4, public_key, algorithms=algorithms) self.assertEqual(obj4.payload, payload) member = {"protected": protected} value5 = jws.serialize_json([member], payload, private_key, algorithms=algorithms) obj5 = jws.deserialize_json(value5, public_key, algorithms=algorithms) self.assertEqual(obj5.payload, payload) self.assertFalse(obj5.flattened) value6 = jws.serialize_json(member, payload, private_key, algorithms=algorithms) obj6 = jws.deserialize_json(value6, public_key, algorithms=algorithms) self.assertEqual(obj6.payload, payload) self.assertTrue(obj6.flattened) # signature won't change with these algorithms if protected["alg"].startswith(("HS", "RS")): self.assertEqual(value4, data["compact"]) self.assertEqual(value5, data["general_json"]) self.assertEqual(value6, data["flattened_json"]) def test_signature_with_detached_content(self): # https://datatracker.ietf.org/doc/html/rfc7520#section-4.5 protected = {"alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"} key = load_key("RFC7520-oct-sig.json") value1 = jws.serialize_compact(protected, payload, key) compact_result = ( "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" "VlZjMxNGJjNzAzNyJ9" ".." "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" ) self.assertEqual(jws.detach_content(value1), compact_result) member = {"protected": protected} value2 = jws.serialize_json([member], payload, key) general_json = { "signatures": [ { "protected": ("eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9"), "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0", } ] } self.assertEqual(jws.detach_content(value2), general_json) flattened_json = { "protected": ("eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9"), "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0", } value3 = jws.serialize_json(member, payload, key) self.assertEqual(jws.detach_content(value3), flattened_json) def test_protecting_specific_header_fields(self): # https://datatracker.ietf.org/doc/html/rfc7520#section-4.6 protected = {"alg": "HS256"} unprotected = {"kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"} member = {"protected": protected, "header": unprotected} key = load_key("RFC7520-oct-sig.json") value1 = jws.serialize_json([member], payload, key) general_json = { "payload": b64_payload, "signatures": [ { "protected": "eyJhbGciOiJIUzI1NiJ9", "header": {"kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"}, "signature": "bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr20", } ], } self.assertEqual(value1, general_json) value2 = jws.serialize_json(member, payload, key) flattened_json = { "payload": b64_payload, "protected": "eyJhbGciOiJIUzI1NiJ9", "header": {"kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"}, "signature": "bWUSVaxorn7bEF1djytBd0kHv70Ly5pvbomzMWSOr20", } self.assertEqual(value2, flattened_json) def test_protecting_content_only(self): # https://datatracker.ietf.org/doc/html/rfc7520#section-4.7 unprotected = {"alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"} key = load_key("RFC7520-oct-sig.json") member = {"header": unprotected} value1 = jws.serialize_json([member], payload, key) general_json = { "payload": b64_payload, "signatures": [ { "header": {"alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"}, "signature": "xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZuk", } ], } self.assertEqual(value1, general_json) value2 = jws.serialize_json(member, payload, key) flattened_json = { "payload": b64_payload, "header": {"alg": "HS256", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037"}, "signature": "xuLifqLGiblpv9zBpuZczWhNj1gARaLV3UxvxhJxZuk", } self.assertEqual(value2, flattened_json) def test_multiple_signatures(self): # https://www.rfc-editor.org/rfc/rfc7520#section-4.8 output = { "payload": ( "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb" "3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGl" "mIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub" "3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4" ), "signatures": [ { "protected": "eyJhbGciOiJSUzI1NiJ9", "header": {"kid": "bilbo.baggins@hobbiton.example"}, "signature": ( "MIsjqtVlOpa71KE-Mss8_Nq2YH4FGhiocsqrgi5NvyG53uoimic1tc" "MdSg-qptrzZc7CG6Svw2Y13TDIqHzTUrL_lR2ZFcryNFiHkSw129Egh" "GpwkpxaTn_THJTCglNbADko1MZBCdwzJxwqZc-1RlpO2HibUYyXSwO9" "7BSe0_evZKdjvvKSgsIqjytKSeAMbhMBdMma622_BG5t4sdbuCHtFjp" "9iJmkio47AIwqkZV1aIZsv33uPUqBBCXbYoQJwt7mxPftHmNlGoOSMx" "R_3thmXTCm4US-xiNOyhbm8afKK64jU6_TPtQHiJeQJxz9G3Tx-083B745_AfYOnlC9w" ), }, { "header": {"alg": "ES512", "kid": "bilbo.baggins@hobbiton.example"}, "signature": ( "ARcVLnaJJaUWG8fG-8t5BREVAuTY8n8YHjwDO1muhcdCoFZFFjfISu0" "Cdkn9Ybdlmi54ho0x924DUz8sK7ZXkhc7AFM8ObLfTvNCrqcI3Jkl2U" "5IX3utNhODH6v7xgy1Qahsn0fyb4zSAkje8bAWz4vIfj5pCMYxxm4fgV3q7ZYhm5eD" ), }, { "protected": "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZjMxNGJjNzAzNyJ9", "signature": "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0", }, ], } def resolve_key(recipient): headers = recipient.headers() if headers["alg"] == "ES512": return load_key("RFC7520-EC-public.json") elif headers["alg"] == "RS256": return load_key("RFC7520-RSA-public.json") return load_key("RFC7520-oct-sig.json") algorithms = ["RS256", "ES512", "HS256"] obj = jws.deserialize_json(output, resolve_key, algorithms=algorithms) self.assertEqual([m.headers()["alg"] for m in obj.members], algorithms) TestJWSRFC7520.load_fixture("jws_rfc7520.json") joserfc-1.1.0/tests/test_util.py000066400000000000000000000022551501424510600166760ustar00rootroot00000000000000import binascii from unittest import TestCase from joserfc import util class TestUtil(TestCase): def test_to_bytes(self): self.assertEqual(util.to_bytes(b"foo"), b"foo") self.assertEqual(util.to_bytes("foo"), b"foo") self.assertEqual(util.to_bytes(123), b"123") self.assertEqual(util.to_bytes([102, 111, 111]), b"foo") def test_to_unicode(self): self.assertEqual(util.to_str(b"foo"), "foo") self.assertEqual(util.to_str("foo"), "foo") def test_int_to_base64(self): self.assertRaises(ValueError, util.int_to_base64, -1) def test_json_b64encode(self): self.assertEqual(util.json_b64encode("{}"), b"e30") def test_urlsafe_b64decode(self): self.assertEqual(util.urlsafe_b64decode(b"_foo123-"), b"\xfd\xfa(\xd7m\xfe") self.assertRaises(binascii.Error, util.urlsafe_b64decode, b"+foo123/") for c in "RSTUVWXYZabdef": # A -> QQ== self.assertRaises(binascii.Error, util.urlsafe_b64decode, b"Q" + c.encode()) for c in "FGH": # AAAAAAAAAAAAAA -> QUFBQUFBQUFBQUFBQUE= self.assertRaises(binascii.Error, util.urlsafe_b64decode, b"QUFBQUFBQUFBQUFBQU" + c.encode())