pax_global_header00006660000000000000000000000064151174443250014520gustar00rootroot0000000000000052 comment=aae77437dc71f988b506b15226aa5aa1e922bc04 authlib-joserfc-aae7743/000077500000000000000000000000001511744432500152165ustar00rootroot00000000000000authlib-joserfc-aae7743/.editorconfig000066400000000000000000000003161511744432500176730ustar00rootroot00000000000000root = 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}] indent_size = 2 authlib-joserfc-aae7743/.github/000077500000000000000000000000001511744432500165565ustar00rootroot00000000000000authlib-joserfc-aae7743/.github/CODE_OF_CONDUCT.md000066400000000000000000000026171511744432500213630ustar00rootroot00000000000000# 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/) authlib-joserfc-aae7743/.github/FUNDING.yml000066400000000000000000000000401511744432500203650ustar00rootroot00000000000000github: - authlib - lepture authlib-joserfc-aae7743/.github/workflows/000077500000000000000000000000001511744432500206135ustar00rootroot00000000000000authlib-joserfc-aae7743/.github/workflows/docs.yml000066400000000000000000000022001511744432500222600ustar00rootroot00000000000000name: 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@v5 - uses: actions/setup-python@v6 with: python-version: "3.13" - 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@v4 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 authlib-joserfc-aae7743/.github/workflows/pypi.yml000066400000000000000000000024441511744432500223230ustar00rootroot00000000000000name: Release to PyPI permissions: contents: write on: push: tags: - "1.*" jobs: build: name: build dist files runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 with: python-version: "3.10" - name: install build run: python -m pip install --upgrade build - name: build dist run: python -m build - uses: actions/upload-artifact@v5 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@v6 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@v5 with: fetch-depth: 0 - uses: actions/setup-node@v6 with: node-version: 24 - run: npx changelogithub --no-group continue-on-error: true env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} authlib-joserfc-aae7743/.github/workflows/test.yml000066400000000000000000000034331511744432500223200ustar00rootroot00000000000000name: Test permissions: contents: read on: push: branches-ignore: - 'wip-*' paths-ignore: - '.github/**' - 'docs/**' - '*.md' - '*.rst' - '*.lock' pull_request: branches-ignore: - 'wip-*' paths-ignore: - '.github/**' - 'docs/**' - '*.md' - '*.rst' - '*.lock' jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-python@v6 with: python-version: "3.13" - 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.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" - "pypy-3.10" - "pypy-3.11" steps: - uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 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@v6 continue-on-error: true env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} authlib-joserfc-aae7743/.gitignore000066400000000000000000000012021511744432500172010ustar00rootroot00000000000000# 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 authlib-joserfc-aae7743/.pre-commit-config.yaml000066400000000000000000000011071511744432500214760ustar00rootroot00000000000000--- repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.14.0' hooks: - id: ruff-check args: [--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] - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v1.18.2' hooks: - id: mypy pass_filenames: false additional_dependencies: - cryptography - pycryptodome authlib-joserfc-aae7743/LICENSE000066400000000000000000000027351511744432500162320ustar00rootroot00000000000000BSD 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. authlib-joserfc-aae7743/MANIFEST.in000066400000000000000000000002161511744432500167530ustar00rootroot00000000000000include LICENSE include README.rst recursive-include tests * # include documentation sources but not built docs graft docs prune docs/_build authlib-joserfc-aae7743/Makefile000066400000000000000000000004721511744432500166610ustar00rootroot00000000000000lang=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 authlib-joserfc-aae7743/README.md000066400000000000000000000065371511744432500165100ustar00rootroot00000000000000
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://img.shields.io/pypi/v/joserfc)](https://pypi.org/project/joserfc) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/joserfc?label=conda-forge&colorB=0090ff)](https://anaconda.org/conda-forge/joserfc) [![PyPI Downloads](https://img.shields.io/pypi/dm/joserfc)](https://pypistats.org/packages/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, jwk from joserfc.jwk import OctKey key = jwk.import_key("your-secret-key", "oct") encoded = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) # 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrIjoidmFsdWUifQ._M8ViO_GK6TnZ9G9eqdlS7IpNWzhoGwaYYDQ3hEwwmA' 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: [JSON Web Key (JWK) Thumbprint](https://jose.authlib.org/en/guide/jwk/#thumbprint) - 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 - RFC9278: [JWK Thumbprint URI](https://jose.authlib.org/en/guide/jwk/#thumbprint-uri) - RFC9864: `Ed25519` and `Ed448` algorithms 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) - draft-ietf-jose-deprecate-none-rsa15-02 ## 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. authlib-joserfc-aae7743/README.rst000066400000000000000000000032011511744432500167010ustar00rootroot00000000000000JOSE 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: JSON Web Key (JWK) Thumbprint - RFC7797: JSON Web Signature (JWS) Unencoded Payload Option - RFC8037: OKP Key and EdDSA algorithm - RFC8812: ES256K algorithm - RFC9278: JWK Thumbprint URI - RFC9864: ``Ed25519`` and ``Ed448`` algorithms And draft RFCs implementation of: - draft-ietf-jose-deprecate-none-rsa15-02 - draft-amringer-jose-chacha-02 - draft-madden-jose-ecdh-1pu-04 Usage ----- A quick and simple JWT encoding and decoding would look something like this: .. code-block:: python >>> from joserfc import jwt, jwk >>> key = jwk.import_key("your-secret-key", "oct") >>> encoded_jwt = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) >>> encoded_jwt 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrIjoidmFsdWUifQ._M8ViO_GK6TnZ9G9eqdlS7IpNWzhoGwaYYDQ3hEwwmA' >>> 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) 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. authlib-joserfc-aae7743/SECURITY.md000066400000000000000000000002521511744432500170060ustar00rootroot00000000000000# Security If you discover a security vulnerability, **do not submit a public issue or patch**. Instead, please report it privately through the **GitHub Security** tab. authlib-joserfc-aae7743/docs/000077500000000000000000000000001511744432500161465ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/Makefile000066400000000000000000000001601511744432500176030ustar00rootroot00000000000000gettext: sphinx-build -b gettext . _build/gettext update-locales: sphinx-intl update -p _build/gettext -l zh authlib-joserfc-aae7743/docs/_static/000077500000000000000000000000001511744432500175745ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/_static/custom.css000066400000000000000000000003371511744432500216230ustar00rootroot00000000000000: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; } authlib-joserfc-aae7743/docs/_static/dark-logo.svg000066400000000000000000000424651511744432500222070ustar00rootroot00000000000000 authlib-joserfc-aae7743/docs/_static/icon.svg000077500000000000000000000012421511744432500212470ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/_static/light-logo.svg000066400000000000000000000424671511744432500223770ustar00rootroot00000000000000 authlib-joserfc-aae7743/docs/api/000077500000000000000000000000001511744432500167175ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/api/errors.rst000066400000000000000000000001631511744432500207650ustar00rootroot00000000000000Errors ====== All errors are based on ``joserfc.errors.JoseError``. .. automodule:: joserfc.errors :members: authlib-joserfc-aae7743/docs/api/index.rst000066400000000000000000000015341511744432500205630ustar00rootroot00000000000000API 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 authlib-joserfc-aae7743/docs/api/jwe.rst000066400000000000000000000002271511744432500202370ustar00rootroot00000000000000.. _jwe_api: JWE API ======= This part of the documentation covers all the interfaces of ``joserfc.jwe``. .. automodule:: joserfc.jwe :members: authlib-joserfc-aae7743/docs/api/jwk.rst000066400000000000000000000002571511744432500202500ustar00rootroot00000000000000.. _jwk_api: JWK API ======= This part of the documentation covers all the interfaces of ``joserfc.jwk``. .. automodule:: joserfc.jwk :members: :inherited-members: authlib-joserfc-aae7743/docs/api/jws.rst000066400000000000000000000002271511744432500202550ustar00rootroot00000000000000.. _jws_api: JWS API ======= This part of the documentation covers all the interfaces of ``joserfc.jws``. .. automodule:: joserfc.jws :members: authlib-joserfc-aae7743/docs/api/jwt.rst000066400000000000000000000002271511744432500202560ustar00rootroot00000000000000.. _jwt_api: JWT API ======= This part of the documentation covers all the interfaces of ``joserfc.jwt``. .. automodule:: joserfc.jwt :members: authlib-joserfc-aae7743/docs/changelog.rst000066400000000000000000000204311511744432500206270ustar00rootroot00000000000000Changelog ========= .. rst-class:: lead Here is the history of joserfc_ package releases. .. _joserfc: https://pypi.org/project/joserfc/ ---- .. module:: joserfc :noindex: 1.6.0 ----- **Released on December 14, 2025** - ``filter_algorithms`` ``names`` defaults to all algorithms. :pull:`79`. - Replace ``JWSRegistry.guess_alg`` with ``JWSRegistry.guess_algorithm``. - ``filter_algorithms`` and ``guess_alg`` supports ``KeySet`` objects. :pull:`81`. - Improve ``generate_private_key`` method on Key's binding class. - Raise ``InvalidKeyCurveError`` when generating ECKey with an invalid curve. - Allow import key from cryptography native key types. - Add ``ECKey.derive_key`` and ``OKPKey.derive_key`` class methods. 1.5.0 ----- **Released on November 30, 2025** - Add ``Ed25519`` and ``Ed448`` algorithms per :ref:`rfc9864`, via :issue:`76`. - Marked ``EdDSA`` algorithm as deprecated per :ref:`rfc9864`, via :issue:`76`. - Add parameter ``default_type`` for :meth:`jwt.encode` method. 1.4.3 ----- **Released on November 19, 2025** - Import ``bytes`` or ``str`` keys without specified key type, via :issue:`73`. 1.4.2 ----- **Released on November 17, 2025** - Remove the original content from ``ExceededSizeError``. 1.4.1 ----- **Released on November 5, 2025** - Add a base ``ClaimError`` for catching JWT claim validation errors. - Show security warnings when importing weak OctKey and RSAKey. 1.4.0 ----- **Released on October 9, 2025** - Improvements on type hints. - Add python 3.14 support. **Breaking changes**: - Remove deprecated (since 1.2.0) ``rfcXXXX`` modules. - Rename ``jwt.ClaimsRegistry`` to ``jwt.BaseClaimsRegistry``. 1.3.5 ----- **Released on November 18, 2025** - Remove the original content from ``ExceededSizeError``. 1.3.4 ----- **Released on September 21, 2025** - Add size limit for deserializing JWS content. - Add size limit for decrypting JWE content. 1.3.3 ----- **Released on September 15, 2025** - Reject ``crit`` header in unprotected headers. 1.3.2 ----- **Released on September 4, 2025** - Returns the first key when multiple keys found in a key set. - Validate if a "crit" header is supported in the registry. 1.3.1 ----- **Released on August 27, 2025** - Fix ``jws.deserialize_json`` and ``jwe.decrypt_json``, preventing unprotected header overwriting protected header. 1.3.0 ----- **Released on August 25, 2025** - Exporting all algorithms in ``joserfc.jwa`` module. - Allow reusing ``JWTClaimsRegistry`` instance, via :issue:`68`. - Added ``claim`` attribute on claim errors, via :issue:`69`. - Added ``JWSRegistry.guess_alg`` method, via :issue:`49`. **Breaking changes**: - Remove Python 3.8 support. - Rename JWS and JWE Algorithm model class names to prevent name conflicts. 1.2.2 ----- **Released on July 14, 2025** - Fix typo for function name of Chacha20-Poly1305 registration, via :pull:`67`. - Add claims partial list matching in ``JWTClaimRegistry.validate``, via :pull:`63`. 1.2.1 ----- **Released on July 10, 2025** - Improve type hints on JWK module: - Overload type hints on ``jwk.import_key`` and ``jwk.generate_key``. - Return correct types on ``OctKey.import_key``, ``RSAKey.import_key``, and etc. - Guess key with "alg" and "use" parameters. 1.2.0 ----- **Released on July 7, 2025** - Added :ref:`rfc9278` JWK Thumbprint URI ``thumbprint_uri``. - Show security warnings for ``none`` and ``RSA1_5`` algorithms. - Show security warnings for ``OctKey.generate_key`` and ``RSAKey.generate_key``. when key size is too short, per `NIST SP 800-131A`_. .. _`NIST SP 800-131A`: https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final **Breaking changes**: - Enable "RFC7797" by default, use the ``joserfc.jws`` module directly. - Use ``joserfc.jws.serialize_compact`` instead of ``joserfc.rfc7797.serialize_compact`` - Use ``joserfc.jws.deserialize_compact`` instead of ``joserfc.rfc7797.deserialize_compact`` - Use ``joserfc.jws.serialize_json`` instead of ``joserfc.rfc7797.serialize_json`` - Use ``joserfc.jws.deserialize_json`` instead of ``joserfc.rfc7797.deserialize_json`` - Convert ``joserfc.rfcXXXX`` to private modules ``joserfc._rfcXXXX``. 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 February 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 February 6, 2025** - Allow using sha256, sha384, sha512 hash functions in thumbprint (RFC7638). 1.0.2 ----- **Released on January 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. authlib-joserfc-aae7743/docs/conf.py000066400000000000000000000060201511744432500174430ustar00rootroot00000000000000from 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", "sphinx_iconify", "shibuya.sponsors", ] iconify_script_url = "" sponsors_json_url = "https://cdn.jsdelivr.net/gh/lepture/lepture/sponsors.json" 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": "Support us", "url": "/en/sponsors"}, ], } 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] if language == "zh": nav_links = html_theme_options["nav_links"] nav_links[0]["title"] = "项目" nav_links[1]["title"] = "赞助我们" nav_links[1]["url"] = "/zh/sponsors" 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"), ] authlib-joserfc-aae7743/docs/contributing/000077500000000000000000000000001511744432500206555ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/contributing/authors.rst000066400000000000000000000004461511744432500231000ustar00rootroot00000000000000Authors ======= ``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 authlib-joserfc-aae7743/docs/contributing/index.rst000066400000000000000000000031111511744432500225120ustar00rootroot00000000000000Contributing ============ 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-dev.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 authlib-joserfc-aae7743/docs/contributing/sponsors.rst000066400000000000000000000000221511744432500232670ustar00rootroot00000000000000Sponsors ======== authlib-joserfc-aae7743/docs/contributing/structure.rst000066400000000000000000000060141511744432500234500ustar00rootroot00000000000000Code 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) _rfc7797/ # Code related to RFC7797 (Unencoded Payload Option) _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. authlib-joserfc-aae7743/docs/contributing/translation.rst000066400000000000000000000022451511744432500237500ustar00rootroot00000000000000: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. authlib-joserfc-aae7743/docs/guide/000077500000000000000000000000001511744432500172435ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/guide/algorithms.rst000066400000000000000000000211071511744432500221470ustar00rootroot00000000000000: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 Requirements ============== ========== ============================ none OctKey :bdg-danger:`Deprecated` HS256 OctKey :bdg-success:`Recommended` HS384 OctKey :bdg-muted:`Optional` HS512 OctKey :bdg-muted:`Optional` RS256 RSAKey :bdg-success:`Recommended` RS384 RSAKey :bdg-muted:`Optional` RS512 RSAKey :bdg-muted:`Optional` ES256 ECKey :bdg-success:`Recommended` ES384 ECKey :bdg-muted:`Optional` ES512 ECKey :bdg-muted:`Optional` PS256 RSAKey :bdg-muted:`Optional` PS384 RSAKey :bdg-muted:`Optional` PS512 RSAKey :bdg-muted:`Optional` EdDSA OKPKey :bdg-danger:`Deprecated` ES256K ECKey :bdg-muted:`Optional` Ed25519 OKPKey :bdg-muted:`Optional` Ed448 OKPKey :bdg-muted:`Optional` ============== ========== ============================ .. versionadded:: 1.5.0 ``Ed25519`` and ``Ed448`` are added since 1.5.0 per RFC9864. 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("your-secret-key") # 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) .. warning:: - ``none`` algorithm is deprecated via https://datatracker.ietf.org/doc/draft-ietf-jose-deprecate-none-rsa15/ - ``EdDSA`` algorithm only accepts ``OKPKey`` with "crv" of "Ed25519" and "Ed448". - ``EdDSA`` algorithm is replaced by "Ed25519" and "Ed448" via RFC 9864. .. _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 Requirements =================== ===================== =========================== dir OctKey :bdg-success:`Recommended` A128KW OctKey (128 bits) :bdg-success:`Recommended` A192KW OctKey (192 bits) :bdg-muted:`Optional` A256KW OctKey (256 bits) :bdg-success:`Recommended` RSA1_5 RSAKey :bdg-danger:`Deprecated` RSA-OAEP RSAKey :bdg-success:`Recommended` RSA-OAEP-256 RSAKey :bdg-muted:`Optional` ECDH-ES ECKey :bdg-success:`Recommended` ECDH-ES+A128KW ECKey :bdg-success:`Recommended` ECDH-ES+A192KW ECKey :bdg-muted:`Optional` ECDH-ES+A256KW ECKey :bdg-success:`Recommended` A128GCMKW OctKey (128 bits) :bdg-muted:`Optional` A192GCMKW OctKey (192 bits) :bdg-muted:`Optional` A256GCMKW OctKey (256 bits) :bdg-muted:`Optional` PBES2-HS256+A128KW RSAKey :bdg-muted:`Optional` PBES2-HS384+A192KW RSAKey :bdg-muted:`Optional` PBES2-HS512+A256KW RSAKey :bdg-muted:`Optional` =================== ===================== =========================== .. warning:: ``RSA1_5`` algorithm is deprecated via https://datatracker.ietf.org/doc/draft-ietf-jose-deprecate-none-rsa15/ Encryption Algorithms ~~~~~~~~~~~~~~~~~~~~~ All algorithms defined in RFC7518 for "enc" value are recommended. =================== =========================== Encryption name Requirements =================== =========================== A128CBC-HS256 :bdg-success:`Recommended` A192CBC-HS384 :bdg-success:`Recommended` A256CBC-HS512 :bdg-success:`Recommended` A128GCM :bdg-success:`Recommended` A192GCM :bdg-success:`Recommended` A256GCM :bdg-success:`Recommended` =================== =========================== There is also a ``DEF`` algorithm for the "zip" (compression) header parameter, using of ``DEF`` is optional. 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_chacha20_poly1305 register_chacha20_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. authlib-joserfc-aae7743/docs/guide/errors.rst000066400000000000000000000057341511744432500213220ustar00rootroot00000000000000Errors & Warnings ================= Here are some common errors and warnings, and how to handle them. SecurityWarning --------------- .. versionadded:: 1.2.0 You may encounter a ``SecurityWarning`` when using potentially unsafe algorithms or generating insecure keys. These warnings do not interrupt the execution of your application — they are simply printed to standard output (e.g., your terminal). If you prefer to suppress these warnings, you can use Python’s built-in ``warnings`` module: .. code-block:: python import warnings from joserfc.errors import SecurityWarning warnings.simplefilter('ignore', SecurityWarning) With this configuration, ``SecurityWarning`` messages will no longer appear. Be cautious when suppressing these warnings, as they are meant to alert you to potentially insecure practices. pytest ~~~~~~ When running unit tests with ``pytest``, you may want to ignore security warnings. In that case, you can configure it in your ``pyproject.toml`` file: .. code-block:: toml :caption: pyproject.toml [tool.pytest.ini_options] filterwarnings = [ "ignore::joserfc.errors.SecurityWarning", ] .. _UnsupportedAlgorithmError: UnsupportedAlgorithmError ------------------------- .. versionchanged:: 1.1.0 From version 1.1.0, an ``UnsupportedAlgorithmError`` will be raised instead of a ``ValueError``. By default, **ONLY recommended** :ref:`jwa` are allowed. With non recommended algorithms, you may encounter the ``UnsupportedAlgorithmError`` error. .. code-block:: python >>> from joserfc import jws >>> from joserfc.jwk import OctKey >>> key = OctKey.generate_key() >>> 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 Because "HS384" is not a recommended algorithm, it is not allowed by default. You SHOULD enable it manually by passing an ``algorithms`` parameter: .. code-block:: python >>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, algorithms=["HS384"]) 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("your-secret-key") >>> registry = jws.JWSRegistry(algorithms=["HS384"]) >>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, registry=registry) 'eyJhbGciOiJIUzM4NCJ9.cGF5bG9hZA.TJEvlp74g89hNRNGNZxCQvB7YDEAWP5vFAjgu1O9Qr5BLMj0NtvbxvYkVYPGp-xQ' authlib-joserfc-aae7743/docs/guide/index.rst000066400000000000000000000047411511744432500211120ustar00rootroot00000000000000: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, jwk >>> key = jwk.import_key("your-secret-key", "oct") >>> encoded_jwt = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) >>> encoded_jwt 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrIjoidmFsdWUifQ._M8ViO_GK6TnZ9G9eqdlS7IpNWzhoGwaYYDQ3hEwwmA' >>> 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() >>> 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 authlib-joserfc-aae7743/docs/guide/introduction.rst000066400000000000000000000041221511744432500225150ustar00rootroot00000000000000: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. - **Full featured**: ``joserfc`` includes implementations for all JOSE-related RFCs, providing complete coverage of the JOSE ecosystem out of the box. 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. authlib-joserfc-aae7743/docs/guide/jwe.rst000066400000000000000000000165321511744432500205710ustar00rootroot00000000000000: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 bit 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 ~~~~~~~~~~ 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. authlib-joserfc-aae7743/docs/guide/jwk.rst000066400000000000000000000322561511744432500206000ustar00rootroot00000000000000: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": "eW91ci1zZWNyZXQta2V5", }) When importing a key, you can add extra parameters into a key: .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("your-secret-key", {"use": "sig"}) >>> key.as_dict() {'k': 'eW91ci1zZWNyZXQta2V5', '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 "RSA" 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" }) Derive an "EC" Key ~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.6.0 ``joserfc`` provides deterministic key derivation helpers for EC key. This method allows applications to derive a *stable* JWK from an application secret (for example, ``SECRET_KEY`` in a web framework), while still producing valid cryptographic keys for use in JWS/JWE. .. code-block:: python from joserfc.jwk import ECKey ECKey.derive_key("your-secret-key", "P-256") ECKey.derive_key("your-secret-key", "P-256", kdf_name="HKDF") Supported key derivation functions: - **HKDF** (recommended) - **PBKDF2** (password-based, may require higher iteration counts) .. warning:: Deterministic keys are useful for applications that want a stable default JWK, but **they should not be used where random key generation is required**. .. _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" }) Derive an "OKP" Key ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.6.0 Just like above ``ECKey``, ``joserfc`` provides a ``OKPKey.derive_key`` method to derive a *stable* JWK. .. code-block:: python from joserfc.jwk import OKPKey OKPKey.derive_key("your-secret-key", "Ed25519") OKPKey.derive_key("your-secret-key", "Ed25519", kdf_name="HKDF") Supported key derivation functions: - **HKDF** (recommended) - **PBKDF2** (password-based, may require higher iteration counts) 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`` ~~~~~~~~~~~~~~ Calling this method will generate the thumbprint of the JWK, per RFC7638. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.generate_key() >>> key.thumbprint() 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' You can also use the ``jwk.thumbprint`` method: .. code-block:: python from joserfc import jwk jwk.thumbprint({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' ``thumbprint_uri`` ~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.2.0 Calling this method will generate the JWK thumbprint URI, per RFC9278. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.generate_key() >>> key.thumbprint_uri() 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' You can also use the ``jwk.thumbprint_uri`` method: .. code-block:: python from joserfc import jwk jwk.thumbprint_uri({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' .. _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("your-secret-key") >>> key.kid None >>> key.ensure_kid() >>> key.kid 'IGfff6pETIMRgwAOHtzIQ_Sflgv5wzWl6SYZlcZ_JDU' ``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(private=False) # 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(private=False) # 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) Utilities --------- The ``jwk`` module offers a means to dynamically import and generate keys. Import keys ~~~~~~~~~~~ .. versionadded:: 1.1.0 The :meth:`import_key` can choose the correct key type automatically when importing a JWK in dict: .. code-block:: python from joserfc import jwk data = {"kty": "oct", "k": "..."} key = jwk.import_key(data) # returns a OctKey data = {"kty": "RSA", ...} key = jwk.import_key(data) # returns a RSAKey data = {"kty": "EC", ...} key = jwk.import_key(data) # returns a ECKey data = {"kty": "OKP", ...} key = jwk.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 = jwk.import_key(data, "RSA") Generate keys ~~~~~~~~~~~~~ .. versionadded:: 1.1.0 The :meth:`generate_key` can generate a key with all the supported key types. For ``oct`` and ``RSA`` the parameters in this method: .. code-block:: python from joserfc import jwk # (key_type: str, size: int, parameters: Optional[dict], private: bool=True) key = jwk.generate_key("oct", 256) key = jwk.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 = jwk.generate_key("EC", "P-256") key = jwk.generate_key("OKP", "Ed25519") Options ------- The ``import_key`` and ``generate_key`` methods available on ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and ``jwk`` 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``). authlib-joserfc-aae7743/docs/guide/jws.rst000066400000000000000000000275351511744432500206140ustar00rootroot00000000000000: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("your-secret-key") protected = {"alg": "HS256"} jws.serialize_compact(protected, "hello", key) # => 'eyJhbGciOiJIUzI1NiJ9.aGVsbG8.i_RrfHeMxwHrhk5Xi3J_bU9B9O-gjkMaQtagtqiCndM' 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.i_RrfHeMxwHrhk5Xi3J_bU9B9O-gjkMaQtagtqiCndM" key = OctKey.import_key("your-secret-key") 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``: .. 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, RFC8812, and RFC9864. Here lists all the algorithms ``joserfc.jws`` supporting: ============== ================================================ ========================= Algorithm name Description Requirements ============== ================================================ ========================= none No digital signature or MAC performed :bdg-danger:`Deprecated` HS256 HMAC using SHA-256 :bdg-success:`Recommended` HS384 HMAC using SHA-384 :bdg-muted:`Optional` HS512 HMAC using SHA-512 :bdg-muted:`Optional` RS256 RSASSA-PKCS1-v1_5 using SHA-256 :bdg-success:`Recommended` RS384 RSASSA-PKCS1-v1_5 using SHA-384 :bdg-muted:`Optional` RS512 RSASSA-PKCS1-v1_5 using SHA-512 :bdg-muted:`Optional` ES256 ECDSA using P-256 and SHA-256 :bdg-success:`Recommended` ES384 ECDSA using P-384 and SHA-384 :bdg-muted:`Optional` ES512 ECDSA using P-521 and SHA-512 :bdg-muted:`Optional` PS256 RSASSA-PSS using SHA-256 and MGF1 with SHA-256 :bdg-muted:`Optional` PS384 RSASSA-PSS using SHA-384 and MGF1 with SHA-384 :bdg-muted:`Optional` PS512 RSASSA-PSS using SHA-512 and MGF1 with SHA-512 :bdg-muted:`Optional` EdDSA Edwards-curve Digital Signature :bdg-danger:`Deprecated` ES256K ECDSA using secp256k1 curve and SHA-256 :bdg-muted:`Optional` Ed25519 EdDSA using the Ed25519 parameter set :bdg-muted:`Optional` Ed448 EdDSA using the Ed448 parameter set :bdg-muted:`Optional` ============== ================================================ ========================= .. versionadded:: 1.5.0 ``Ed25519`` and ``Ed448`` are added since 1.5.0 per RFC9864. 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 an :ref:`UnsupportedAlgorithmError` error. .. _unencoded_payload: 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. Here are examples demonstrating the usage of the ``b64`` option: .. code-block:: python from joserfc.jws import serialize_compact, deserialize_compact from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") 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.jws import serialize_compact, deserialize_compact from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") protected = {"alg": "HS256", "b64": False, "crit": ["b64"]} value = serialize_compact(protected, "$.02", key) # => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6iWgAK_TZLgwzk1mhtxs6Imw-dJM1cRstsOKVQ5MjFQ' # since the payload is detached, you need to specify the # payload when calling deserialize_compact deserialize_compact(value, key, payload="$.02") You can also use ``b64`` header for JSON serialization: ``serialize_json`` and ``deserialize_json``. Guess Algorithms via Key ------------------------ If you are unsure which algorithm to use but already have a key, you can call the :meth:`jws.JWSRegistry.guess_algorithm` method to determine a suitable algorithm: .. code-block:: python from joserfc.jws import JWSRegistry, serialize_compact alg = JWSRegistry.guess_algorithm(key, JWSRegistry.Strategy.RECOMMENDED) protected = {"alg": alg.name} serialize_compact(protected, b"payload", key) authlib-joserfc-aae7743/docs/guide/jwt.rst000066400000000000000000000406021511744432500206030ustar00rootroot00000000000000: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("your-secret-key") 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. Prevent sensitive data leaks ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before calling :meth:`encode` on your claims, it's a good practice to ensure they do not contain sensitive information, such as credit card numbers. You can use :meth:`check_sensitive_data` to detect whether sensitive data is present in the claims: .. code-block:: python from joserfc import jwt jwt.check_sensitive_data(claims) Convert datetime ~~~~~~~~~~~~~~~~ :meth:`encode` will convert ``datetime`` to timestamp integer for ``iat``, ``exp``, and ``nbf``: .. code-block:: python import datetime from joserfc import jwk, jwt now = datetime.datetime.now(datetime.UTC) claims = { "iss": "https://authlib.org", "iat": now, "exp": now + datetime.timedelta(minutes=5), } header = {"alg": "HS256"} key = jwk.import_key("your-secret-key", "oct") text = jwt.encode(header, claims, key) 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.errors import ClaimError, ExpiredTokenError, InvalidTokenError from joserfc.jwt import JWTClaimsRegistry claims_requests = JWTClaimsRegistry( iss={"essential": True, "value": "https://authlib.org"}, ) # usually you will use the claims registry after ``.decode`` try: claims_requests.validate(token.claims) except ClaimError as error: print(error) except ExpiredTokenError as error: print("expired") except InvalidTokenError as error: # only happens when iat and nbf invalid print("invalid") 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 .. code-block:: python claims = {"iss": "https://authlib.org", "exp": 1765256501} claims_requests = JWTClaimsRegistry() claims_requests.validate(claims) # this will raise ExpiredTokenError List validation ~~~~~~~~~~~~~~~ When validating claims that contain lists, the registry checks if **any** of the required values are present in the claim's list. This behavior is designed for flexible authorization checks where matching any of the required permissions grants access. For single values, it checks for an exact match. This is particularly useful for validating role based or permission based claims. For example: .. code-block:: python # Claim containing a list of permissions claims = {"permissions": ["users:read", "users:write", "users:admin"]} # Passes since "users:write" is present in the list claims_requests = JWTClaimsRegistry( permissions={"values": ["users:write", "system:admin"]} ) claims_requests.validate(claims) # Raises InvalidClaimError since none of the required values are present claims_requests = JWTClaimsRegistry( permissions={"values": ["system:admin"]} ) claims_requests.validate(claims) You can also validate against a single required value: .. code-block:: python # Claim containing a list of permissions claims = {"permissions": ["users:read", "users:write", "users:admin"]} # Passes since "users:read" is present in the list claims_requests = JWTClaimsRegistry( permissions={"value": "users:read"} ) claims_requests.validate(claims) Custom validation ----------------- When it's not possible to validate a claim using ``ClaimsOption``, you can define a custom validation method named ``validate_{name}``. For example, if the claims must include a ``source`` field, and the value of ``source`` must be an HTTPS URL, you can implement a custom method to enforce this requirement. .. code-block:: python from joserfc.jwt import JWTClaimsRegistry from joserfc.errors import InvalidClaimError class MyClaimsRegistry(JWTClaimsRegistry): def validate_source(self, value): if not value.startswith('https://'): raise InvalidClaimError('source') Then, you can validate the claims with: .. code-block:: python claims_requests = MyClaimsRegistry(source={"essential": True}) 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 one 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-site/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) Embedded JWK ~~~~~~~~~~~~ The key may be embedded directly in the token's header. For example, the decoded header might look like this: .. code-block:: json { "jwk": { "crv": "P-256", "x": "UM9g5nKnZXYovWAlM76cLz9vTozRj__CHU_dOl-gOoE", "y": "ds8aeQw1l2cDCA7bCkONvwDKpXAbtXjvqCleYH8Ws_U", "kty": "EC" }, "alg": "ES256" } In such cases, you don't need to supply a separate key manually. Instead, as shown above, you can use a callable key function to dynamically resolve the embedded JWK value. .. code-block:: python from joserfc import jwk def embedded_jwk(obj: jwk.GuestProtocol) -> jwk.Key: headers = obj.headers() return jwk.import_key(headers["jwk"]) # jwt.decode(value, embedded_jwk) Embedded JWK Set URL ~~~~~~~~~~~~~~~~~~~~ As shown above, the key may also be provided as a JWK Set URL within the token header, for example: .. code-block:: json { "jku": "https://example-site/jwks.json", "alg": "ES256" } In this case, you can use a callable key function to import the JWKs: .. code-block:: python import requests from joserfc.jwk import GuestProtocol, Key, KeySet def fetch_jwk_set(obj: GuestProtocol) -> Key: headers = obj.headers() resp = requests.get(headers["jku"]) return KeySet.import_key_set(resp.json()) jwt.decode(value, fetch_jwk_set) .. hint:: Use a cache method to improve the performance. Algorithms & Registry --------------------- The :meth:`encode` and :meth:`decode` accept an ``algorithms`` parameter for specifying the allowed algorithms. By default, it only allows you to use the **recommended** algorithms. You can find out the recommended algorithms at: - :ref:`JSON Web Signature Algorithms ` - :ref:`JSON Web Encryption Algorithms ` For instance, ``HS384`` 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("your-secret-key") >>> 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("your-secret-key") >>> 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("your-secret-key") 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. authlib-joserfc-aae7743/docs/guide/registry.rst000066400000000000000000000150541511744432500216520ustar00rootroot00000000000000: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("your-secret-key") >>> 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("your-secret-key") >>> 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("your-secret-key") >>> 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("your-secret-key") 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``. authlib-joserfc-aae7743/docs/index.rst000066400000000000000000000053101511744432500200060ustar00rootroot00000000000000JOSE 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, jwk >>> key = jwk.import_key("your-secret-key", "oct") >>> encoded = jwt.encode({"alg": "HS256"}, {"k": "value"}, key) >>> encoded 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrIjoidmFsdWUifQ._M8ViO_GK6TnZ9G9eqdlS7IpNWzhoGwaYYDQ3hEwwmA' >>> 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 ``"your-secret-key"`` 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: - :ref:`rfc7515`: :ref:`JSON Web Signature ` - :ref:`rfc7516`: :ref:`JSON Web Encryption ` - :ref:`rfc7517`: :ref:`JSON Web Key ` - :ref:`rfc7518`: :ref:`JSON Web Algorithms ` - :ref:`rfc7519`: :ref:`JSON Web Token ` - :ref:`rfc7520`: Examples of Protecting Content Using JSON Object Signing and Encryption - :ref:`rfc7638`: ``thumbprint`` for JWK - :ref:`rfc7797`: JSON Web Signature (JWS) :ref:`Unencoded Payload Option ` - :ref:`rfc8037`: :ref:`OKPKey` and ``EdDSA`` algorithm - :ref:`rfc8812`: ``ES256K`` algorithm and ``ECKey`` with curve ``secp256k1`` - :ref:`rfc9278`: JWK Thumbprint URI (``thumbprint_uri``) - :ref:`rfc9864`: ``Ed25519`` and ``Ed448`` algorithms 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 guide/errors migrations/index .. toctree:: :caption: Recipes :hidden: recipes/azure recipes/openssl .. toctree:: :caption: Development :hidden: api/index rfc/index security stability contributing/index changelog authlib-joserfc-aae7743/docs/install.rst000066400000000000000000000037041511744432500203520ustar00rootroot00000000000000: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/ Package install --------------- ``joserfc`` is conveniently available as a Python package on PyPI_ and conda-forge_. It can be easily installed via: .. tab-set:: :class: outline .. tab-item:: :iconify:`devicon:pypi` pip .. code-block:: shell pip install joserfc .. tab-item:: :iconify:`material-icon-theme:uv` uv .. code-block:: shell uv add joserfc .. tab-item:: :iconify:`devicon:anaconda` conda .. code-block:: shell conda install conda-forge::joserfc .. important:: To use :ref:`chacha20` algorithms, developers have to install the ``PyCryptodome`` module. .. code-block:: shell pip install joserfc pycryptodome .. _PyPI: https://pypi.org/project/joserfc/ .. _conda-forge: https://anaconda.org/conda-forge/joserfc 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 authlib-joserfc-aae7743/docs/locales/000077500000000000000000000000001511744432500175705ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/locales/zh/000077500000000000000000000000001511744432500202115ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/000077500000000000000000000000001511744432500217765ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/api.po000066400000000000000000001442421511744432500231160ustar00rootroot00000000000000# 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-12-09 14:11+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. It is raised when signature does not " "match." msgstr "该错误是为 JWS 设计的,当签名不匹配时触发。" #: ../../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.InvalidKeyCurveError.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.KeyParameterError.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.ClaimError:1 of msgid "This a base error for JWT claims validation." msgstr "" #: ../../docstring joserfc.errors.ClaimError.description:1 #: joserfc.errors.ExpiredTokenError.description:1 #: joserfc.errors.InsecureClaimError.description:1 #: joserfc.errors.InvalidCEKLengthError.description:1 #: joserfc.errors.InvalidClaimError.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.MissingClaimError.description:1 #: joserfc.errors.MissingEncryptionError.description:1 of msgid "long-string to describe this error" msgstr "描述此错误的长字符串" #: joserfc.errors.DecodeError:1 of msgid "" "This error is designed for both JWS and JWE. It is raised when " "deserialization and decryption fails." msgstr "该错误是为 JWS/JWE 设计的,当反序列化或解密失败时触发。" #: joserfc.errors.ExceededSizeError:1 of msgid "" "This error is designed for validating the token's content size. It raised" " when the data exceeds the maximum allowed length." msgstr "该错误是为 DEF 压缩算法设计的,当压缩数据超过允许的最大长度时触发。" #: joserfc.errors.ExpiredTokenError:1 of msgid "This error is designed for JWT. It raised when the token is expired." msgstr "该错误是为 JWT 设计的,令牌过期时触发。" #: joserfc.errors.InsecureClaimError:1 of msgid "" "This error is designed for JWT. It raised when the claim contains " "sensitive information." msgstr "该错误是为 JWT 设计的,当声明(claim)包含敏感信息时会触发。" #: joserfc.errors.InvalidClaimError:1 of msgid "" "This error is designed for JWT. It raised when the claim contains invalid" " values or types." msgstr "该错误是为 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.InvalidExchangeKeyError:1 of msgid "" "This error is designed for EC and OKP keys. It is raised when exchanging " "derive key failed." msgstr "此错误用于 EC 和 OKP 密钥,在密钥派生交换失败时触发。" #: joserfc.errors.InvalidHeaderValueError:1 of msgid "Raised when the given header's value is invalid." msgstr "当给定的 header 值无效时触发该错误。" #: joserfc.errors.InvalidKeyCurveError:1 of msgid "" "This error is designed for JWS. It is raised when key's curve name does " "not match with the given algorithm." msgstr "此错误用于 JWS,当密钥(EC、OKP)的曲线名称与指定算法不匹配时触发。" #: joserfc.errors.InvalidKeyIdError:1 of msgid "" "This error is designed for Key Set. It is raised when a key can not be " "found with the given key ID." msgstr "此错误用于密钥集,当无法根据给定的密钥 ID 找到对应密钥时触发。" #: joserfc.errors.InvalidKeyLengthError:1 of msgid "" "This error is designed for JWE. It is raised when key's length does not " "align with the given algorithm." msgstr "此错误用于 JWE。当密钥长度与指定算法不匹配时触发。" #: joserfc.errors.InvalidPayloadError:1 of msgid "" "This error is designed for JWT. It raised when the payload is not a valid" " JSON object." msgstr "此错误用于 JWT。当载荷(payload)不是有效的 JSON 对象时触发。" #: joserfc.errors.InvalidTokenError:1 of msgid "This error is designed for JWT. It raised when the token is not valid yet." msgstr "此错误是为 JWT 设计的,当 Token 不是有效的 JSON 对象时触发。" #: joserfc.errors.JoseError:1 of msgid "Base Exception for all errors in joserfc." msgstr "joserfc 中所有错误的基类异常。" #: joserfc.errors.MissingAlgorithmError:1 of msgid "Raised when an algorithm (\"alg\") is missing." msgstr "当缺少算法(\"alg\")时触发。" #: joserfc.errors.MissingClaimError:1 of msgid "" "This error is designed for JWT. It raised when the required claims are " "missing." msgstr "此错误是为 JWT 设计的,当缺少必要的声明时会触发。" #: joserfc.errors.MissingCritHeaderError:1 of msgid "This error happens when the critical header does not exist." msgstr "当关键(``crit``) header 值不存在时触发。" #: joserfc.errors.MissingEncryptionError:1 of msgid "" "This error is designed for JWE. It is raised when the 'enc' value in " "header is missing." msgstr "此错误是为 JWE 设计的,当 header 中缺少 'enc' 值时会触发。" #: joserfc.errors.MissingHeaderError:1 of msgid "This error happens when the required header does not exist." msgstr "当 header 中缺少必要数据时会触发此错误。" #: joserfc.errors.SecurityWarning:1 of msgid "Base class for warnings of security issues." msgstr "" #: joserfc.errors.UnsupportedAlgorithmError:1 of msgid "" "This error is designed for both JWS and JWE. It is raised when the given " "algorithm is not supported in the registry." msgstr "此错误适用于 JWS 和 JWE,当注册表中不支持给定算法时触发。" #: joserfc.errors.UnsupportedHeaderError:1 of msgid "Raised when an unsupported header is encountered." msgstr "当遇到不支持的 header 值时触发。" #: ../../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" #: joserfc._rfc7516.models.CompactEncryption.headers:1 of msgid "Returns the protected header values 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``" #: ../../docstring joserfc.jwe.JWERegistry.max_auth_tag_length:1 of msgid "max auth tag's size in bytes" msgstr "" #: ../../docstring joserfc.jwe.JWERegistry.max_ciphertext_length:1 of msgid "max ciphertext's size in bytes" msgstr "" #: ../../docstring joserfc.jwe.JWERegistry.max_encrypted_key_length:1 of msgid "max encrypted key's size in bytes" msgstr "" #: ../../docstring joserfc.jwe.JWERegistry.max_initialization_vector_length:1 #: of msgid "max initialization vector's size in bytes" msgstr "" #: ../../docstring joserfc.jwe.JWERegistry.max_protected_header_length:1 of msgid "max protected header content's size in bytes" msgstr "受保护的 header 内容的最大字节大小" #: 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.jwk.ECKey.alg:1 joserfc.jwk.OKPKey.alg:1 joserfc.jwk.OctKey.alg:1 #: joserfc.jwk.RSAKey.alg:1 of msgid "The \"alg\" value of the JSON Web Key." msgstr "" #: joserfc._rfc7517.models.BaseKey.as_dict:1 of msgid "" "Output this key to a JWK format (in dict). By default, it will return the" " ``dict_value`` of this key." msgstr "" #: joserfc._rfc7517.models.BaseKey.as_dict:4 of msgid "determine whether this method should output private key or not" msgstr "确定此方法是否应输出私钥" #: joserfc._rfc7517.models.BaseKey.as_dict:5 of msgid "other parameters added into this key" msgstr "添加到此密钥的其他参数" #: joserfc._rfc7517.models.BaseKey.as_dict of msgid "raise" msgstr "触发" #: joserfc._rfc7517.models.BaseKey.as_dict:6 of msgid "ValueError" msgstr "ValueError" #: joserfc._rfc7517.models.BaseKey.check_alg:1 of msgid "Check if this key supports the given \"alg\"." msgstr "检查此密钥是否支持给定的 \"alg\"。" #: joserfc._rfc7517.models.BaseKey.check_alg:3 of msgid "the algorithm this key is intended to be used, e.g. \"HS256\", \"ECDH-EC\"" msgstr "此密钥预期使用的算法,例如 \"HS256\"、\"ECDH-EC\"" #: ../../api/jwk.rst ../../api/jws.rst ../../api/jwt.rst msgid "Raises" msgstr "触发" #: joserfc._rfc7517.models.BaseKey.check_alg:4 of msgid "if this key is not designed for the given algorithm" msgstr "如果此密钥不能用于给定的算法" #: joserfc._rfc7517.models.BaseKey.check_key_op:1 of msgid "Check if the given key_op is supported by this key." msgstr "检查此密钥是否支持给定的 key_op。" #: joserfc._rfc7517.models.BaseKey.check_key_op:3 of msgid "key operation value, such as \"sign\", \"encrypt\"." msgstr "密钥操作的值,例如 \"sign\"、\"encrypt\"。" #: joserfc._rfc7517.models.BaseKey.check_key_op:4 of msgid "if the operation is not supported by this key." msgstr "如果此密钥不支持给定的 key_op。" #: joserfc._rfc7517.models.BaseKey.check_use:1 of msgid "Check if this key supports the given \"use\"." msgstr "检查此密钥是否支持给定的 \"use\"。" #: joserfc._rfc7517.models.BaseKey.check_use:3 of msgid "Values defined by this specification are:" msgstr "" #: joserfc._rfc7517.models.BaseKey.check_use:5 of msgid "\"sig\" (signature)" msgstr "\"sig\" (签名)" #: joserfc._rfc7517.models.BaseKey.check_use:6 of msgid "\"enc\" (encryption)" msgstr "\"enc\" (加密)" #: joserfc._rfc7517.models.BaseKey.check_use:8 of msgid "" "Other values MAY be used. The \"use\" value is a case-sensitive string. " "Use of the \"use\" member is OPTIONAL, unless the application requires " "its presence." msgstr "可以使用其他值。\"use\" 值区分大小写。除非应用程序要求其存在,否则 \"use\" 成员是可选的。" #: joserfc._rfc7517.models.BaseKey.check_use:12 of msgid "this key is used for, e.g. \"sig\", \"enc\"" msgstr "此密钥用于,例如 \"sig\"、\"enc\"" #: joserfc._rfc7517.models.BaseKey.check_use:13 of msgid "if this key is not designed for the given use" msgstr "如果此密钥不支持给定的 \"use\"" #: joserfc.jwk.ECKey.dict_value:1 joserfc.jwk.OKPKey.dict_value:1 #: joserfc.jwk.OctKey.dict_value:1 joserfc.jwk.RSAKey.dict_value:1 of msgid "Property of the Key in Dict (JSON)." msgstr "字典形式(JSON)的密钥的属性值。" #: joserfc._rfc7517.models.BaseKey.ensure_kid:1 of msgid "" "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." msgstr "" "确保此密钥具有 ``kid``。如果默认未提供 ``kid``,将使用 ``.thumbprint`` 方法生成 kid,该方法由 RFC7638" " 定义。" #: 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``" #: joserfc.jwk.ECKey.kid:1 joserfc.jwk.OKPKey.kid:1 joserfc.jwk.OctKey.kid:1 #: joserfc.jwk.RSAKey.kid:1 of msgid "The \"kid\" value of the JSON Web Key." msgstr "" #: joserfc._rfc7517.models.BaseKey.thumbprint:1 of msgid "" "Call this method will generate the thumbprint with algorithm defined in " "RFC7638." msgstr "" #: joserfc._rfc7517.models.BaseKey.thumbprint_uri:1 of msgid "Call this method will generate the thumbprint URI defined in RFC9278." msgstr "" #: ../../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.jwk.KeyParameters.fromkeys:1 #: joserfc.jwk.KeySetSerialization.fromkeys:1 of msgid "Create a new dictionary with keys from iterable and values set to value." msgstr "" #: joserfc.jwk.KeyParameters.get:1 joserfc.jwk.KeyParameters.setdefault:3 #: joserfc.jwk.KeySetSerialization.get:1 #: joserfc.jwk.KeySetSerialization.setdefault:3 of msgid "Return the value for key if key is in the dictionary, else default." msgstr "" #: joserfc.jwk.KeyParameters.pop:1 joserfc.jwk.KeySetSerialization.pop:1 of msgid "" "If the key is not found, return the default if given; otherwise, raise a " "KeyError." msgstr "" #: joserfc.jwk.KeyParameters.popitem:1 #: joserfc.jwk.KeySetSerialization.popitem:1 of msgid "Remove and return a (key, value) pair as a 2-tuple." msgstr "" #: joserfc.jwk.KeyParameters.popitem:3 #: joserfc.jwk.KeySetSerialization.popitem:3 of msgid "" "Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if" " the dict is empty." msgstr "" #: joserfc.jwk.KeyParameters.setdefault:1 #: joserfc.jwk.KeySetSerialization.setdefault:1 of msgid "Insert key with a value of default if key is not in the dictionary." msgstr "" #: joserfc.jwk.KeyParameters.update:1 joserfc.jwk.KeySetSerialization.update:1 #: of msgid "" "If E is present and has a .keys() method, then does: for k in E.keys(): " "D[k] = E[k] If E is present and lacks a .keys() method, then does: for " "k, v in E: D[k] = v In either case, this is followed by: for k in F: " "D[k] = F[k]" msgstr "" #: 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" #: joserfc.jwk.OctKey.is_private:1 of msgid "A symmetric key will always be private." msgstr "对称密钥始终是私有的。" #: joserfc.jwk.OctKey.private_key:1 of msgid "Returns the ``raw_value`` as the private key." msgstr "" #: joserfc.jwk.OctKey.public_key:1 of msgid "Returns the ``raw_value`` as the public key." msgstr "" #: joserfc.jwk.OctKey.raw_value:1 of msgid "The raw key in bytes." msgstr "原始密钥,字节形式。" #: ../../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 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.guess_key:6 of msgid "optional \"use\" value" msgstr "可选的 \"use\" 值" #: joserfc.jwk.import_key:1 of 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``。" #: joserfc._rfc7638.calculate_thumbprint:1 of msgid "Calculate the thumbprint value of a Key, per RFC 7638." msgstr "根据 RFC 7638 计算密钥的指纹值。" #: joserfc._rfc9278.calculate_thumbprint_uri:1 of msgid "Calculate JWK thumbprint URI, defined by RFC9278." msgstr "根据 RFC9278 计算 JWK 指纹值的 URI。" #: ../../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.CompactSignature.headers:1 of msgid "Returns protected header values in dict." msgstr "返回字典形式的受保护的 header 值。" #: ../../docstring joserfc.jws.CompactSignature.payload:1 #: joserfc.jws.FlattenedJSONSignature.payload:1 #: joserfc.jws.GeneralJSONSignature.payload:1 of msgid "payload content in bytes" msgstr "字节形式的载荷内容" #: ../../docstring joserfc.jws.CompactSignature.protected:1 #: joserfc.jws.HeaderMember.protected:1 of msgid "protected header" msgstr "受保护的 header" #: 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 "标记为扁平" #: joserfc._rfc7515.model.FlattenedJSONSignature.headers:1 of msgid "Header values in dict." msgstr "字典形式的 header 值。" #: ../../docstring joserfc.jws.FlattenedJSONSignature.member:1 of msgid "the only header member" msgstr "唯一的 header 成员" #: joserfc.jws.FlattenedJSONSignature.members:1 of msgid "" "A list of header members. For flattened JSON serialization, there will be" " only one header member." 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" #: 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`` 中的方法一起使用。" #: ../../docstring joserfc.jws.JWSRegistry.Strategy.RECOMMENDED:1 of msgid "find the recommended algorithm" msgstr "" #: ../../docstring joserfc.jws.JWSRegistry.Strategy.SECURITY:1 of msgid "find the most secure algorithm" msgstr "" #: joserfc._rfc7515.registry.JWSRegistry.filter_algorithms:1 of msgid "Filter JWS algorithms based on the given algorithm names." msgstr "" #: joserfc._rfc7515.registry.JWSRegistry.filter_algorithms:3 of msgid "a key instance or a KeySet" msgstr "" #: joserfc._rfc7515.registry.JWSRegistry.filter_algorithms:4 of msgid "list of algorithm names" msgstr "算法名称列表" #: 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.guess_algorithm:1 of msgid "Guess the JWS algorithm for a given key." msgstr "为给定密钥推测 JWS 算法。" #: joserfc._rfc7515.registry.JWSRegistry.guess_algorithm:3 of msgid "key instance or a KeySet" msgstr "" #: joserfc._rfc7515.registry.JWSRegistry.guess_algorithm:4 of msgid "the strategy for guessing the JWS algorithm" msgstr "" #: ../../docstring joserfc.jws.JWSRegistry.max_header_length:1 of msgid "max header content's size in bytes" msgstr "header 内容的最大字节大小" #: ../../docstring joserfc.jws.JWSRegistry.max_payload_length:1 of msgid "max payload content's size in bytes" msgstr "payload 内容的最大字节大小" #: ../../docstring joserfc.jws.JWSRegistry.max_signature_length:1 of msgid "max signature's size in bytes" msgstr "签名的最大字节大小" #: 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._rfc7797.compact.extract_rfc7515_compact:4 #: joserfc.jws.deserialize_compact:18 of msgid "optional payload, required with detached content" msgstr "" #: joserfc.jws.deserialize_compact:19 joserfc.jws.deserialize_json:8 #: joserfc.jwt.decode:9 of msgid "when signature verification fails" msgstr "" #: joserfc.jws.deserialize_compact:20 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 GeneralJSONSignature or FlattenedJSONSignature" msgstr "" #: 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._rfc7797.compact.extract_rfc7515_compact:1 of msgid "Extract the JWS Compact Serialization from bytes to object." msgstr "从字节中提取 JWS 紧凑序列化为对象。" #: joserfc._rfc7797.compact.extract_rfc7515_compact:3 of msgid "JWS in bytes" msgstr "字节形式的 JWS" #: joserfc._rfc7797.compact.extract_rfc7515_compact:5 of msgid "optional JWSRegistry instance" msgstr "可选的 JWSRegistry 实例" #: joserfc._rfc7797.compact.extract_rfc7515_compact:6 of msgid "when decoding fails" msgstr "" #: 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.claims.BaseClaimsRegistry:1 of msgid "Requesting \"claims\" for JWT with the given conditions." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.check_value:1 of msgid "Validates a given claim value based on predefined options." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.check_value:3 of msgid "The name of the claim to validate." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.check_value:4 of msgid "The value of the claim to be validated." msgstr "要验证的声明值。" #: joserfc._rfc7519.claims.BaseClaimsRegistry.check_value:5 of msgid "If the value does not meet the claim's validation requirements." msgstr "" #: joserfc.jwt.BaseClaimsRegistry.essential_keys:1 of msgid "Returns the essential claim names." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.validate:1 of msgid "Validates the provided claims against specified requirements and checks." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.validate:3 of msgid "A dictionary containing claims to validate." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.validate:4 of msgid "Raised if any claim fails validation." msgstr "" #: joserfc._rfc7519.claims.BaseClaimsRegistry.validate:5 of msgid "Raised if one or more essential keys are missing." msgstr "" #: joserfc._rfc7519.claims.JWTClaimsRegistry:1 of msgid "A claims registry for validating JWT claims." msgstr "" #: joserfc._rfc7519.claims.JWTClaimsRegistry:3 of msgid "timestamp of \"now\" time" msgstr "" #: joserfc._rfc7519.claims.JWTClaimsRegistry:4 of msgid "leeway time in seconds" msgstr "" #: joserfc._rfc7519.claims.JWTClaimsRegistry:5 of msgid "claims options" msgstr "" #: joserfc.jwt.JWTClaimsRegistry.now:1 of msgid "Returns the current timestamp." msgstr "" #: joserfc._rfc7519.claims.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.claims.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.claims.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.security.check_sensitive_data:1 of msgid "" "Checks for sensitive data within a dictionary of claims and raises an " "error if any sensitive names or values are detected." msgstr "" #: joserfc._rfc7519.security.check_sensitive_data:4 of msgid "JWT claims to check for sensitive data" msgstr "" #: joserfc._rfc7519.security.check_sensitive_data:5 of msgid "if any sensitive names or values are detected" 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:10 of msgid "when payload is not a valid JSON object" msgstr "当载荷(payload)不是有效的 JSON 对象" #: 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 "要使用的 JSONEncoder 子类" #: joserfc.jwt.encode:9 of msgid "default value of the ``typ`` header parameter" msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/changelog.po000066400000000000000000000412741511744432500242750ustar00rootroot00000000000000# 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-12-09 14:11+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.x.x" msgstr "" #: ../../changelog.rst:18 msgid "**Unreleased**" msgstr "" #: ../../changelog.rst:20 msgid "``filter_algorithms`` ``names`` defaults to all algorithms. :pull:`79`." msgstr "" #: ../../changelog.rst:21 msgid "Replace ``JWSRegistry.guess_alg`` with ``JWSRegistry.guess_algorithm``." msgstr "" #: ../../changelog.rst:22 msgid "" "``filter_algorithms`` and ``guess_alg`` supports ``KeySet`` objects. " ":pull:`81`." msgstr "" #: ../../changelog.rst:23 msgid "Improve ``generate_private_key`` method on Key's binding class." msgstr "" #: ../../changelog.rst:24 msgid "" "Raise ``InvalidKeyCurveError`` when generating ECKey with an invalid " "curve." msgstr "" #: ../../changelog.rst:25 msgid "Allow import key from cryptography native key types." msgstr "" #: ../../changelog.rst:28 msgid "1.5.0" msgstr "" #: ../../changelog.rst:30 msgid "**Released on November 30, 2025**" msgstr "" #: ../../changelog.rst:32 msgid "" "Add ``Ed25519`` and ``Ed448`` algorithms per :ref:`rfc9864`, via " ":issue:`76`." msgstr "" #: ../../changelog.rst:33 msgid "" "Marked ``EdDSA`` algorithm as deprecated per :ref:`rfc9864`, via " ":issue:`76`." msgstr "" #: ../../changelog.rst:34 msgid "Add parameter ``default_type`` for :meth:`jwt.encode` method." msgstr "" #: ../../changelog.rst:37 msgid "1.4.3" msgstr "" #: ../../changelog.rst:39 msgid "**Released on November 19, 2025**" msgstr "" #: ../../changelog.rst:41 msgid "" "Import ``bytes`` or ``str`` keys without specified key type, via " ":issue:`73`." msgstr "" #: ../../changelog.rst:44 msgid "1.4.2" msgstr "" #: ../../changelog.rst:46 msgid "**Released on November 17, 2025**" msgstr "" #: ../../changelog.rst:48 ../../changelog.rst:76 msgid "Remove the original content from ``ExceededSizeError``." msgstr "" #: ../../changelog.rst:51 msgid "1.4.1" msgstr "" #: ../../changelog.rst:53 msgid "**Released on November 5, 2025**" msgstr "" #: ../../changelog.rst:55 msgid "Add a base ``ClaimError`` for catching JWT claim validation errors." msgstr "" #: ../../changelog.rst:56 msgid "Show security warnings when importing weak OctKey and RSAKey." msgstr "" #: ../../changelog.rst:59 msgid "1.4.0" msgstr "" #: ../../changelog.rst:61 msgid "**Released on October 9, 2025**" msgstr "" #: ../../changelog.rst:63 msgid "Improvements on type hints." msgstr "" #: ../../changelog.rst:64 msgid "Add python 3.14 support." msgstr "" #: ../../changelog.rst:66 ../../changelog.rst:120 ../../changelog.rst:158 #: ../../changelog.rst:179 ../../changelog.rst:292 msgid "**Breaking changes**:" msgstr "" #: ../../changelog.rst:68 msgid "Remove deprecated (since 1.2.0) ``rfcXXXX`` modules." msgstr "" #: ../../changelog.rst:69 msgid "Rename ``jwt.ClaimsRegistry`` to ``jwt.BaseClaimsRegistry``." msgstr "" #: ../../changelog.rst:72 msgid "1.3.5" msgstr "" #: ../../changelog.rst:74 msgid "**Released on November 18, 2025**" msgstr "" #: ../../changelog.rst:79 msgid "1.3.4" msgstr "" #: ../../changelog.rst:81 msgid "**Released on September 21, 2025**" msgstr "" #: ../../changelog.rst:83 msgid "Add size limit for deserializing JWS content." msgstr "" #: ../../changelog.rst:84 msgid "Add size limit for decrypting JWE content." msgstr "" #: ../../changelog.rst:88 msgid "1.3.3" msgstr "" #: ../../changelog.rst:90 msgid "**Released on September 15, 2025**" msgstr "" #: ../../changelog.rst:92 msgid "Reject ``crit`` header in unprotected headers." msgstr "" #: ../../changelog.rst:95 msgid "1.3.2" msgstr "" #: ../../changelog.rst:97 msgid "**Released on September 4, 2025**" msgstr "" #: ../../changelog.rst:99 msgid "Returns the first key when multiple keys found in a key set." msgstr "" #: ../../changelog.rst:100 msgid "Validate if a \"crit\" header is supported in the registry." msgstr "" #: ../../changelog.rst:103 msgid "1.3.1" msgstr "" #: ../../changelog.rst:105 msgid "**Released on August 27, 2025**" msgstr "" #: ../../changelog.rst:107 msgid "" "Fix ``jws.deserialize_json`` and ``jwe.decrypt_json``, preventing " "unprotected header overwriting protected header." msgstr "" #: ../../changelog.rst:111 msgid "1.3.0" msgstr "" #: ../../changelog.rst:113 msgid "**Released on August 25, 2025**" msgstr "" #: ../../changelog.rst:115 msgid "Exporting all algorithms in ``joserfc.jwa`` module." msgstr "" #: ../../changelog.rst:116 msgid "Allow reusing ``JWTClaimsRegistry`` instance, via :issue:`68`." msgstr "" #: ../../changelog.rst:117 msgid "Added ``claim`` attribute on claim errors, via :issue:`69`." msgstr "" #: ../../changelog.rst:118 msgid "Added ``JWSRegistry.guess_alg`` method, via :issue:`49`." msgstr "" #: ../../changelog.rst:122 msgid "Remove Python 3.8 support." msgstr "" #: ../../changelog.rst:123 msgid "Rename JWS and JWE Algorithm model class names to prevent name conflicts." msgstr "" #: ../../changelog.rst:127 msgid "1.2.2" msgstr "" #: ../../changelog.rst:129 msgid "**Released on July 14, 2025**" msgstr "" #: ../../changelog.rst:131 msgid "" "Fix typo for function name of Chacha20-Poly1305 registration, via " ":pull:`67`." msgstr "" #: ../../changelog.rst:132 msgid "" "Add claims partial list matching in ``JWTClaimRegistry.validate``, via " ":pull:`63`." msgstr "" #: ../../changelog.rst:135 msgid "1.2.1" msgstr "" #: ../../changelog.rst:137 msgid "**Released on July 10, 2025**" msgstr "" #: ../../changelog.rst:139 msgid "Improve type hints on JWK module:" msgstr "" #: ../../changelog.rst:141 msgid "Overload type hints on ``jwk.import_key`` and ``jwk.generate_key``." msgstr "" #: ../../changelog.rst:142 msgid "" "Return correct types on ``OctKey.import_key``, ``RSAKey.import_key``, and" " etc." msgstr "" #: ../../changelog.rst:144 msgid "Guess key with \"alg\" and \"use\" parameters." msgstr "" #: ../../changelog.rst:147 msgid "1.2.0" msgstr "" #: ../../changelog.rst:149 msgid "**Released on July 7, 2025**" msgstr "" #: ../../changelog.rst:151 msgid "Added :ref:`rfc9278` JWK Thumbprint URI ``thumbprint_uri``." msgstr "" #: ../../changelog.rst:152 msgid "Show security warnings for ``none`` and ``RSA1_5`` algorithms." msgstr "" #: ../../changelog.rst:153 msgid "" "Show security warnings for ``OctKey.generate_key`` and " "``RSAKey.generate_key``. when key size is too short, per `NIST SP " "800-131A`_." msgstr "" #: ../../changelog.rst:160 msgid "Enable \"RFC7797\" by default, use the ``joserfc.jws`` module directly." msgstr "" #: ../../changelog.rst:162 msgid "" "Use ``joserfc.jws.serialize_compact`` instead of " "``joserfc.rfc7797.serialize_compact``" msgstr "" #: ../../changelog.rst:163 msgid "" "Use ``joserfc.jws.deserialize_compact`` instead of " "``joserfc.rfc7797.deserialize_compact``" msgstr "" #: ../../changelog.rst:164 msgid "" "Use ``joserfc.jws.serialize_json`` instead of " "``joserfc.rfc7797.serialize_json``" msgstr "" #: ../../changelog.rst:165 msgid "" "Use ``joserfc.jws.deserialize_json`` instead of " "``joserfc.rfc7797.deserialize_json``" msgstr "" #: ../../changelog.rst:167 msgid "Convert ``joserfc.rfcXXXX`` to private modules ``joserfc._rfcXXXX``." msgstr "" #: ../../changelog.rst:170 msgid "1.1.0" msgstr "" #: ../../changelog.rst:172 msgid "**Released on May 24, 2025**" msgstr "" #: ../../changelog.rst:174 msgid "Use \"import as\" to prioritize the modules for editors." msgstr "" #: ../../changelog.rst:175 msgid "" "Added parameter ``encoder_cls`` for ``jwt.encode`` and ``decoder_cls`` " "for ``jwt.decode``." msgstr "" #: ../../changelog.rst:176 msgid "Added ``none`` algorithm for JWS." msgstr "" #: ../../changelog.rst:177 msgid "Added ``jwk.import_key`` and ``jwk.generate_key`` aliases." msgstr "" #: ../../changelog.rst:181 msgid "Use ``ECKey.binding.register_curve`` to register new supported curves." msgstr "" #: ../../changelog.rst:182 msgid "" "Use ``UnsupportedAlgorithmError`` instead of ``ValueError`` in JWS/JWE " "registry." msgstr "" #: ../../changelog.rst:183 msgid "Use ``MissingKeyTypeError`` and ``InvalidKeyIdError`` for errors in JWK." msgstr "" #: ../../changelog.rst:184 msgid "" "Use ``UnsupportedHeaderError``, ``MissingHeaderError``, and " "``MissingCritHeaderError`` for header validation." msgstr "" #: ../../changelog.rst:185 msgid "Respect RFC6749 character set in error descriptions." msgstr "" #: ../../changelog.rst:188 msgid "1.0.4" msgstr "" #: ../../changelog.rst:190 msgid "**Released on February 28, 2025**" msgstr "" #: ../../changelog.rst:192 msgid "Use secrets module to generate random bytes." msgstr "" #: ../../changelog.rst:193 msgid "" "Use warnings for possible unsafe ``OctKey`` instead of raising error, via" " :issue:`32`." msgstr "" #: ../../changelog.rst:196 msgid "1.0.3" msgstr "" #: ../../changelog.rst:198 msgid "**Released on February 6, 2025**" msgstr "" #: ../../changelog.rst:200 msgid "Allow using sha256, sha384, sha512 hash functions in thumbprint (RFC7638)." msgstr "" #: ../../changelog.rst:203 msgid "1.0.2" msgstr "" #: ../../changelog.rst:205 msgid "**Released on January 20, 2025**" msgstr "" #: ../../changelog.rst:207 msgid "Support import key from a certificate pem file." msgstr "" #: ../../changelog.rst:210 msgid "1.0.1" msgstr "" #: ../../changelog.rst:212 msgid "**Released on December 3, 2024**" msgstr "" #: ../../changelog.rst:214 msgid "Throw an error on non-valid base64 strings." msgstr "" #: ../../changelog.rst:217 msgid "1.0.0" msgstr "" #: ../../changelog.rst:219 msgid "**Released on July 14, 2024**" msgstr "" #: ../../changelog.rst:221 msgid "Fix type hints for strict mode." msgstr "" #: ../../changelog.rst:224 msgid "0.12.0" msgstr "" #: ../../changelog.rst:226 msgid "**Released on June 15, 2024**" msgstr "" #: ../../changelog.rst:228 msgid "Limit DEF decompress size to 250k bytes." msgstr "" #: ../../changelog.rst:229 msgid "Fix claims validation, via :issue:`23`." msgstr "" #: ../../changelog.rst:232 msgid "0.11.1" msgstr "" #: ../../changelog.rst:234 ../../changelog.rst:241 msgid "**Released on June 4, 2024**" msgstr "" #: ../../changelog.rst:236 msgid "Remove validating ``typ`` header with ``jwt.decode`` method." msgstr "" #: ../../changelog.rst:239 msgid "0.11.0" msgstr "" #: ../../changelog.rst:243 msgid "``jwe.decrypt_json`` allows to verify only one recipient." msgstr "" #: ../../changelog.rst:244 msgid "Prevent ``OctKey`` to import ``ssh-dss``." msgstr "" #: ../../changelog.rst:245 msgid "Deprecate use of string and bytes as key." msgstr "" #: ../../changelog.rst:248 msgid "0.10.0" msgstr "" #: ../../changelog.rst:250 msgid "**Released on May 13, 2024**" msgstr "" #: ../../changelog.rst:252 msgid "Change ``jwt.encode`` and ``jwt.decode`` to use JWS by default." msgstr "" #: ../../changelog.rst:255 msgid "0.9.0" msgstr "" #: ../../changelog.rst:257 msgid "**Released on November 16, 2023**" msgstr "" #: ../../changelog.rst:259 msgid "Use ``os.urandom`` for ``OctKey.generate_key``." msgstr "" #: ../../changelog.rst:260 msgid "Add ``allow_blank`` for ``JWTClaimsRegistry``." msgstr "" #: ../../changelog.rst:261 msgid "Improve callable key for :meth:`~jwk.guess_key`." msgstr "" #: ../../changelog.rst:264 msgid "0.8.0" msgstr "" #: ../../changelog.rst:266 msgid "**Released on September 06, 2023**" msgstr "" #: ../../changelog.rst:268 msgid "Add :ref:`ensure_kid` method on key models." msgstr "" #: ../../changelog.rst:269 msgid "Add ``auto_kid`` parameter on key model ``.generate_key`` method." msgstr "" #: ../../changelog.rst:270 ../../changelog.rst:280 msgid "Improvements on type hints" msgstr "" #: ../../changelog.rst:273 msgid "0.7.0" msgstr "" #: ../../changelog.rst:275 msgid "**Released on August 14, 2023**" msgstr "" #: ../../changelog.rst:277 msgid "Add \"iat\" claims validation in JWT." msgstr "" #: ../../changelog.rst:278 msgid "Add ``__bool__`` magic method on :class:`jwk.KeySet`." msgstr "" #: ../../changelog.rst:279 msgid "" "Raise ``InvalidExchangeKeyError`` for ``exchange_derive_key`` on Curve " "key." msgstr "" #: ../../changelog.rst:283 msgid "0.6.0" msgstr "" #: ../../changelog.rst:285 msgid "**Released on July 20, 2023**" msgstr "" #: ../../changelog.rst:287 msgid "Huge improvements on type hints, via :user:`Viicos`." msgstr "" #: ../../changelog.rst:288 msgid "Do not mutate the header when ``jwt.encode``, via :issue:`6`." msgstr "" #: ../../changelog.rst:289 msgid "Register algorithms with their matched key types on key set." msgstr "" #: ../../changelog.rst:290 msgid "Improve error handling, raise proper errors." msgstr "" #: ../../changelog.rst:294 msgid "" "``jws.JSONSignature`` is replaced by :class:`jws.GeneralJSONSignature` " "and :class:`jws.FlattenedJSONSignature`." msgstr "" #: ../../changelog.rst:296 msgid "" "``jwe.JSONEncryption`` is replaced by :class:`jwe.GeneralJSONEncryption` " "and :class:`jwe.FlattenedJSONEncryption`." msgstr "" #: ../../changelog.rst:300 msgid "0.5.0" msgstr "" #: ../../changelog.rst:302 msgid "**Released on July 12, 2023**" msgstr "" #: ../../changelog.rst:304 msgid "Add RFC7797 JSON Web Signature (JWS) Unencoded Payload Option" msgstr "" #: ../../changelog.rst:305 msgid "Fix ``decrypt_json`` when there is no ``encrypted_key``" msgstr "" #: ../../changelog.rst:306 msgid "Rename JWE CompleteJSONSerialization to GeneralJSONSerialization" msgstr "" #: ../../changelog.rst:307 msgid "Rename ``JSONEncryption.flatten`` to ``.flattened``" msgstr "" #: ../../changelog.rst:308 msgid "Load and dump RSA, EC, and OKP key with password" msgstr "" #: ../../changelog.rst:309 msgid "" "Rename Curve key method: ``exchange_shared_key`` to " "``exchange_derive_key``" msgstr "" #: ../../changelog.rst:312 msgid "0.4.0" msgstr "" #: ../../changelog.rst:314 msgid "**Released on July 6, 2023**" msgstr "" #: ../../changelog.rst:316 msgid "Change ``options`` to ``parameters`` for JWK methods" msgstr "" #: ../../changelog.rst:317 msgid "Change ``JWSRegistry`` and ``JWERegistry`` parameters" msgstr "" #: ../../changelog.rst:318 msgid "Guess ``sender_key`` from JWKs in JWE" msgstr "" #: ../../changelog.rst:319 msgid "Add importing key from DER encoding bytes" msgstr "" #: ../../changelog.rst:320 msgid "Fix JWS JSON serialization when members have only unprotected headers" msgstr "" #: ../../changelog.rst:321 msgid "Check key type before processing algorithms of JWS and JWE" msgstr "" #: ../../changelog.rst:324 msgid "0.3.0" msgstr "" #: ../../changelog.rst:326 msgid "**Released on June 29, 2023**" msgstr "" #: ../../changelog.rst:328 msgid "Return ``str`` instead of ``bytes`` for JWS and JWE serializations" msgstr "" #: ../../changelog.rst:329 msgid "Add a ``detach_content`` method for JWS" msgstr "" #: ../../changelog.rst:330 msgid "Remove ``jwt.extract`` method, because ``extract`` won't work for JWE" msgstr "" #: ../../changelog.rst:331 msgid "Add ``JWKRegistry`` for JWK" msgstr "" #: ../../changelog.rst:332 msgid "Update ``JSONEncryption.add_recipient`` parameters" msgstr "" #: ../../changelog.rst:333 msgid "Export register methods for JWE drafts" msgstr "" #: ../../changelog.rst:336 msgid "0.2.0" msgstr "" #: ../../changelog.rst:338 msgid "**Released on June 25, 2023**" msgstr "" #: ../../changelog.rst:340 msgid "A beta release." msgstr "" #: ../../changelog.rst:343 msgid "0.1.0" msgstr "" #: ../../changelog.rst:345 msgid "**Released on March 5, 2023**" msgstr "" #: ../../changelog.rst:347 msgid "Initial release." msgstr "" #~ msgid "" #~ "Improve type hints on JWK module: " #~ "- Overload type hints on " #~ "``jwk.import_key`` and ``jwk.generate_key``. - " #~ "Return correct types on ``OctKey.import_key``," #~ " ``RSAKey.import_key``, and etc." #~ msgstr "" #~ msgid "Added RFC9278 JWK Thumbprint URI ``thumbprint_uri``." #~ msgstr "" #~ msgid "" #~ "Enable \"RFC7797\" by default, use the" #~ " ``joserfc.jws`` module directly. - Use " #~ "``joserfc.jws.serialize_compact`` instead of " #~ "``joserfc.rfc7797.serialize_compact`` - Use " #~ "``joserfc.jws.deserialize_compact`` instead of " #~ "``joserfc.rfc7797.deserialize_compact`` - Use " #~ "``joserfc.jws.serialize_json`` instead of " #~ "``joserfc.rfc7797.serialize_json`` - Use " #~ "``joserfc.jws.deserialize_json`` instead of " #~ "``joserfc.rfc7797.deserialize_json``" #~ msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/contributing.po000066400000000000000000000222041511744432500250450ustar00rootroot00000000000000# 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 "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/guide.po000066400000000000000000001654671511744432500234560ustar00rootroot00000000000000# 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-12-09 14:11+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:235 #: ../../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:68 ../../guide/jwk.rst:6 msgid "JSON Web Key" msgstr "JSON Web Key" #: ../../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:80 ../../guide/jws.rst:6 msgid "JSON Web Signature" msgstr "JSON Web Signature" #: ../../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:94 #: ../../guide/jws.rst:241 msgid "Algorithm name" msgstr "算法名" #: ../../guide/algorithms.rst:37 ../../guide/algorithms.rst:94 msgid "Key Type" msgstr "密钥类型" #: ../../guide/algorithms.rst:37 ../../guide/algorithms.rst:94 #: ../../guide/jws.rst:241 msgid "Requirements" msgstr "" #: ../../guide/algorithms.rst:39 ../../guide/jws.rst:243 msgid "none" msgstr "none" #: ../../guide/algorithms.rst:39 ../../guide/algorithms.rst:40 #: ../../guide/algorithms.rst:41 ../../guide/algorithms.rst:42 #: ../../guide/algorithms.rst:96 ../../guide/algorithms.rst:97 #: ../../guide/algorithms.rst:98 ../../guide/algorithms.rst:99 #: ../../guide/algorithms.rst:107 ../../guide/algorithms.rst:108 #: ../../guide/algorithms.rst:109 ../../guide/jwk.rst:19 msgid "OctKey" msgstr "OctKey" #: ../../guide/algorithms.rst:39 ../../guide/algorithms.rst:52 #: ../../guide/algorithms.rst:100 ../../guide/jws.rst:243 #: ../../guide/jws.rst:256 msgid ":bdg-danger:`Deprecated`" msgstr ":bdg-danger:`弃用`" #: ../../guide/algorithms.rst:40 ../../guide/jws.rst:244 msgid "HS256" msgstr "HS256" #: ../../guide/algorithms.rst:40 ../../guide/algorithms.rst:43 #: ../../guide/algorithms.rst:46 ../../guide/algorithms.rst:96 #: ../../guide/algorithms.rst:97 ../../guide/algorithms.rst:99 #: ../../guide/algorithms.rst:101 ../../guide/algorithms.rst:103 #: ../../guide/algorithms.rst:104 ../../guide/algorithms.rst:106 #: ../../guide/jws.rst:244 ../../guide/jws.rst:247 ../../guide/jws.rst:250 msgid ":bdg-success:`Recommended`" msgstr ":bdg-success:`推荐`" #: ../../guide/algorithms.rst:41 ../../guide/jws.rst:245 msgid "HS384" msgstr "HS384" #: ../../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:53 #: ../../guide/algorithms.rst:54 ../../guide/algorithms.rst:55 #: ../../guide/algorithms.rst:98 ../../guide/algorithms.rst:102 #: ../../guide/algorithms.rst:105 ../../guide/algorithms.rst:107 #: ../../guide/algorithms.rst:108 ../../guide/algorithms.rst:109 #: ../../guide/algorithms.rst:110 ../../guide/algorithms.rst:111 #: ../../guide/algorithms.rst:112 ../../guide/jws.rst:245 #: ../../guide/jws.rst:246 ../../guide/jws.rst:248 ../../guide/jws.rst:249 #: ../../guide/jws.rst:251 ../../guide/jws.rst:252 ../../guide/jws.rst:253 #: ../../guide/jws.rst:254 ../../guide/jws.rst:255 ../../guide/jws.rst:257 #: ../../guide/jws.rst:258 ../../guide/jws.rst:259 msgid ":bdg-muted:`Optional`" msgstr ":bdg-muted:`可选`" #: ../../guide/algorithms.rst:42 ../../guide/jws.rst:246 msgid "HS512" msgstr "HS512" #: ../../guide/algorithms.rst:43 ../../guide/jws.rst:247 msgid "RS256" msgstr "RS256" #: ../../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:100 ../../guide/algorithms.rst:101 #: ../../guide/algorithms.rst:102 ../../guide/algorithms.rst:110 #: ../../guide/algorithms.rst:111 ../../guide/algorithms.rst:112 #: ../../guide/jwk.rst:64 msgid "RSAKey" msgstr "RSAKey" #: ../../guide/algorithms.rst:44 ../../guide/jws.rst:248 msgid "RS384" msgstr "RS384" #: ../../guide/algorithms.rst:45 ../../guide/jws.rst:249 msgid "RS512" msgstr "RS512" #: ../../guide/algorithms.rst:46 ../../guide/jws.rst:250 msgid "ES256" msgstr "ES256" #: ../../guide/algorithms.rst:46 ../../guide/algorithms.rst:47 #: ../../guide/algorithms.rst:48 ../../guide/algorithms.rst:53 #: ../../guide/algorithms.rst:103 ../../guide/algorithms.rst:104 #: ../../guide/algorithms.rst:105 ../../guide/algorithms.rst:106 #: ../../guide/jwk.rst:125 msgid "ECKey" msgstr "ECKey" #: ../../guide/algorithms.rst:47 ../../guide/jws.rst:251 msgid "ES384" msgstr "ES384" #: ../../guide/algorithms.rst:48 ../../guide/jws.rst:252 msgid "ES512" msgstr "ES512" #: ../../guide/algorithms.rst:49 ../../guide/jws.rst:253 msgid "PS256" msgstr "PS256" #: ../../guide/algorithms.rst:50 ../../guide/jws.rst:254 msgid "PS384" msgstr "PS384" #: ../../guide/algorithms.rst:51 ../../guide/jws.rst:255 msgid "PS512" msgstr "PS512" #: ../../guide/algorithms.rst:52 ../../guide/jws.rst:256 msgid "EdDSA" msgstr "EdDSA" #: ../../guide/algorithms.rst:52 ../../guide/algorithms.rst:54 #: ../../guide/algorithms.rst:55 ../../guide/algorithms.rst:135 #: ../../guide/jwk.rst:180 msgid "OKPKey" msgstr "OKPKey" #: ../../guide/algorithms.rst:53 ../../guide/jws.rst:257 msgid "ES256K" msgstr "ES256K" #: ../../guide/algorithms.rst:54 ../../guide/jws.rst:258 msgid "Ed25519" msgstr "" #: ../../guide/algorithms.rst:55 ../../guide/jws.rst:259 msgid "Ed448" msgstr "" #: ../../guide/algorithms.rst:60 ../../guide/jws.rst:264 msgid "``Ed25519`` and ``Ed448`` are added since 1.5.0 per RFC9864." msgstr "" #: ../../guide/algorithms.rst:62 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 "" "默认情况下,JWS 的 ``serialize`` 和 ``deserialize`` 方法仅允许使用推荐算法。要使用非推荐算法,开发者必须通过 " "``algorithms`` 参数或 ``registry`` 参数显式指定算法。" #: ../../guide/algorithms.rst:81 msgid "" "``none`` algorithm is deprecated via https://datatracker.ietf.org/doc" "/draft-ietf-jose-deprecate-none-rsa15/" msgstr "" "``none`` 算法已被弃用,详见 https://datatracker.ietf.org/doc/draft-ietf-jose-" "deprecate-none-rsa15/" #: ../../guide/algorithms.rst:82 msgid "" "``EdDSA`` algorithm only accepts ``OKPKey`` with \"crv\" of \"Ed25519\" " "and \"Ed448\"." msgstr "``EdDSA`` 算法只接受具有 \"crv\" 为 \"Ed25519\" 和 \"Ed448\" 的 ``OKPKey``。" #: ../../guide/algorithms.rst:83 msgid "``EdDSA`` algorithm is replaced by \"Ed25519\" and \"Ed448\" via RFC 9864." msgstr "``EdDSA`` 算法已依据 RFC 9864 被 \"Ed25519\" 和 \"Ed448\" 所取代。" #: ../../guide/algorithms.rst:88 ../../guide/index.rst:86 ../../guide/jwe.rst:6 msgid "JSON Web Encryption" msgstr "JSON Web Encryption" #: ../../guide/algorithms.rst:90 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:96 msgid "dir" msgstr "dir" #: ../../guide/algorithms.rst:97 msgid "A128KW" msgstr "A128KW" #: ../../guide/algorithms.rst:98 msgid "A192KW" msgstr "A192KW" #: ../../guide/algorithms.rst:99 msgid "A256KW" msgstr "A256KW" #: ../../guide/algorithms.rst:100 msgid "RSA1_5" msgstr "RSA1_5" #: ../../guide/algorithms.rst:101 msgid "RSA-OAEP" msgstr "RSA-OAEP" #: ../../guide/algorithms.rst:102 msgid "RSA-OAEP-256" msgstr "RSA-OAEP-256" #: ../../guide/algorithms.rst:103 ../../guide/algorithms.rst:140 msgid "ECDH-ES" msgstr "ECDH-ES" #: ../../guide/algorithms.rst:104 ../../guide/algorithms.rst:141 msgid "ECDH-ES+A128KW" msgstr "ECDH-ES+A128KW" #: ../../guide/algorithms.rst:105 ../../guide/algorithms.rst:142 msgid "ECDH-ES+A192KW" msgstr "ECDH-ES+A192KW" #: ../../guide/algorithms.rst:106 ../../guide/algorithms.rst:143 msgid "ECDH-ES+A256KW" msgstr "ECDH-ES+A256KW" #: ../../guide/algorithms.rst:107 msgid "A128GCMKW" msgstr "A128GCMKW" #: ../../guide/algorithms.rst:108 msgid "A192GCMKW" msgstr "A192GCMKW" #: ../../guide/algorithms.rst:109 msgid "A256GCMKW" msgstr "A256GCMKW" #: ../../guide/algorithms.rst:110 msgid "PBES2-HS256+A128KW" msgstr "PBES2-HS256+A128KW" #: ../../guide/algorithms.rst:111 msgid "PBES2-HS384+A192KW" msgstr "PBES2-HS384+A192KW" #: ../../guide/algorithms.rst:112 msgid "PBES2-HS512+A256KW" msgstr "PBES2-HS512+A256KW" #: ../../guide/algorithms.rst:115 msgid "" "All algorithms defined in RFC7518 for \"enc\" value are recommended, " "which including:" msgstr "RFC7518 中定义的所有用于 \"enc\" 值的算法都是推荐使用的,包括:" #: ../../guide/algorithms.rst:118 msgid "``A128CBC-HS256``" msgstr "``A128CBC-HS256``" #: ../../guide/algorithms.rst:119 msgid "``A192CBC-HS384``" msgstr "``A192CBC-HS384``" #: ../../guide/algorithms.rst:120 msgid "``A256CBC-HS512``" msgstr "``A256CBC-HS512``" #: ../../guide/algorithms.rst:121 msgid "``A128GCM``" msgstr "``A128GCM``" #: ../../guide/algorithms.rst:122 msgid "``A192GCM``" msgstr "``A192GCM``" #: ../../guide/algorithms.rst:123 msgid "``A256GCM``" msgstr "``A256GCM``" #: ../../guide/algorithms.rst:126 msgid "" "``RSA1_5`` algorithm is deprecated via https://datatracker.ietf.org/doc" "/draft-ietf-jose-deprecate-none-rsa15/" msgstr "" "``RSA1_5`` 算法已被弃用,详见 https://datatracker.ietf.org/doc/draft-ietf-jose-" "deprecate-none-rsa15/" #: ../../guide/algorithms.rst:128 msgid "" "There is also a ``DEF`` algorithm for the \"zip\" (compression) header " "parameter, using of ``DEF`` is optional." msgstr "对于 \"zip\"(压缩)头部参数,也有一个 ``DEF`` 算法,使用 ``DEF`` 是可选的。" #: ../../guide/algorithms.rst:131 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:137 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:145 msgid "" "This allows you to utilize these elliptic curve algorithms with " "``OKPKey`` for your cryptographic operations." msgstr "这允许你在加密操作中使用 ``OKPKey`` 来利用这些椭圆曲线算法。" #: ../../guide/algorithms.rst:151 msgid "C20P and XC20P" msgstr "C20P 与 XC20P" #: ../../guide/algorithms.rst:153 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:160 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:164 msgid "Register ciphers" msgstr "注册密码算法" #: ../../guide/algorithms.rst:166 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:176 msgid "Use custom ``registry``" msgstr "自定义注册表 ``registry``" #: ../../guide/algorithms.rst:181 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:206 msgid "ECDH-1PU algorithms" msgstr "ECDH-1PU 相关算法" #: ../../guide/algorithms.rst:208 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:218 msgid "" "Then use a custom ``registry`` with the required ``ECDH-1PU`` algorithms." " For instance:" msgstr "然后使用带有所需的 ``ECDH-1PU`` 算法的自定义 ``registry``。例如:" #: ../../guide/algorithms.rst:241 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:244 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/errors.rst:2 msgid "Errors & Warnings" msgstr "错误与警告" #: ../../guide/errors.rst:4 msgid "Here are some common errors and warnings, and how to handle them." msgstr "以下是一些常见的错误和警告,以及如何处理它们。" #: ../../guide/errors.rst:7 msgid "SecurityWarning" msgstr "SecurityWarning" #: ../../guide/errors.rst:11 msgid "" "You may encounter a ``SecurityWarning`` when using potentially unsafe " "algorithms or generating insecure keys. These warnings do not interrupt " "the execution of your application — they are simply printed to standard " "output (e.g., your terminal)." msgstr "" #: ../../guide/errors.rst:16 msgid "" "If you prefer to suppress these warnings, you can use Python’s built-in " "``warnings`` module:" msgstr "" #: ../../guide/errors.rst:26 msgid "" "With this configuration, ``SecurityWarning`` messages will no longer " "appear. Be cautious when suppressing these warnings, as they are meant to" " alert you to potentially insecure practices." msgstr "" #: ../../guide/errors.rst:31 msgid "pytest" msgstr "" #: ../../guide/errors.rst:33 msgid "" "When running unit tests with ``pytest``, you may want to ignore security " "warnings. In that case, you can configure it in your ``pyproject.toml`` " "file:" msgstr "" #: ../../guide/errors.rst:37 msgid "pyproject.toml" msgstr "" #: ../../guide/errors.rst:48 msgid "UnsupportedAlgorithmError" msgstr "UnsupportedAlgorithmError" #: ../../guide/errors.rst:52 msgid "" "From version 1.1.0, an ``UnsupportedAlgorithmError`` will be raised " "instead of a ``ValueError``." msgstr "" #: ../../guide/errors.rst:55 msgid "" "By default, **ONLY recommended** :ref:`jwa` are allowed. With non " "recommended algorithms, you may encounter the " "``UnsupportedAlgorithmError`` error." msgstr "" #: ../../guide/errors.rst:73 msgid "" "Because \"HS384\" is not a recommended algorithm, it is not allowed by " "default. You SHOULD enable it manually by passing an ``algorithms`` " "parameter:" msgstr "" #: ../../guide/errors.rst:80 msgid "" "Developers can also apply the ``registry`` parameter to resolve this " "issue. Here is an example of using :ref:`registry`." msgstr "" #: ../../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:27 msgid "Learn the details of :ref:`jwt` in the next chapter." msgstr "您可以在下一章节中了解 :ref:`jwt` 的详细信息。" #: ../../guide/index.rst:30 msgid "Import and generate JWK" msgstr "JWK 的导入与导出" #: ../../guide/index.rst:57 msgid "Learn the details of :ref:`jwk` in the next chapter." msgstr "您可以在下一章节中了解 :ref:`jwk` 的详细信息。" #: ../../guide/index.rst:60 msgid "Dive deep" msgstr "深入了解" #: ../../guide/index.rst:62 msgid "Next, learn each module in details." msgstr "接下来,详细学习下面的每个模块。" #: ../../guide/index.rst:72 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:74 ../../guide/jwt.rst:6 msgid "JSON Web Token" msgstr "JSON Web Token" #: ../../guide/index.rst:78 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:84 msgid "Most :ref:`jwt` are encoded with JWS in compact serialization." msgstr "通常我们见到的 :ref:`jwt` 都是基于 JWS 的紧凑序列化编码。" #: ../../guide/index.rst:90 msgid "" "JSON Web Encryption (JWE) represents encrypted content using JSON-based " "data structures." msgstr "JSON Web Encryption (JWE) 使用基于 JSON 的数据结构表示加密内容。" #: ../../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:28 msgid "" "**Full featured**: ``joserfc`` includes implementations for all JOSE-" "related RFCs, providing complete coverage of the JOSE ecosystem out of " "the box." msgstr "" #: ../../guide/introduction.rst:32 msgid "Why joserfc?" msgstr "为何创建 joserfc" #: ../../guide/introduction.rst:34 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:40 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:154 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:114 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:136 msgid "If you prefer adding recipient keys from existing key set:" msgstr "" #: ../../guide/jwe.rst:156 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:173 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:176 msgid "" "You can also change the default behavior to bypass the decryption with " "only one recipient get verified:" msgstr "" #: ../../guide/jwe.rst:185 ../../guide/jws.rst:176 msgid "General and Flattened" msgstr "" #: ../../guide/jwe.rst:187 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:190 msgid "" "The syntax of a JWE using the flattened JWE JSON Serialization is as " "follows:" msgstr "" #: ../../guide/jwe.rst:205 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:213 msgid "And make sure only adding one recipient." msgstr "同时注意,你只能增加一位成员。" #: ../../guide/jwe.rst:216 ../../guide/jwt.rst:472 msgid "Algorithms & Registry" msgstr "算法和注册表" #: ../../guide/jwe.rst:218 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:222 msgid "" "It is possible to support non-recommended algorithms by passing the " "``algorithms`` parameter, or with a custom ``registry``." msgstr "" #: ../../guide/jwe.rst:232 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 \"RSA\" 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 "Key Set" #: ../../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 "``thumbprint``" #: ../../guide/jwk.rst:300 msgid "Calling this method will generate the thumbprint of the JWK, per RFC7638." msgstr "" #: ../../guide/jwk.rst:309 msgid "You can also use the ``jwk.thumbprint`` method:" msgstr "你也可以使用 ``jwk.thumbprint`` 方法:" #: ../../guide/jwk.rst:322 msgid "``thumbprint_uri``" msgstr "``thumbprint_uri``" #: ../../guide/jwk.rst:326 msgid "Calling this method will generate the JWK thumbprint URI, per RFC9278." msgstr "" #: ../../guide/jwk.rst:335 msgid "You can also use the ``jwk.thumbprint_uri`` method:" msgstr "你也可以使用 ``jwk.thumbprint_uri`` 方法:" #: ../../guide/jwk.rst:349 msgid "``ensure_kid``" msgstr "``ensure_kid``" #: ../../guide/jwk.rst:351 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:365 msgid "``as_dict``" msgstr "``as_dict``" #: ../../guide/jwk.rst:367 msgid "" "Dump a key or key set into dict format, which can be used to convert to " "JSON:" msgstr "" #: ../../guide/jwk.rst:377 msgid "``as_pem``" msgstr "``as_pem``" #: ../../guide/jwk.rst:379 msgid "Dump an asymmetric key into PEM format (in bytes):" msgstr "将非对称密钥转换为 PEM 格式(数据为字节形式):" #: ../../guide/jwk.rst:390 msgid "``as_der``" msgstr "``as_der``" #: ../../guide/jwk.rst:392 msgid "Dump an asymmetric key into DER format (in bytes):" msgstr "" #: ../../guide/jwk.rst:403 msgid "Utilities" msgstr "" #: ../../guide/jwk.rst:405 msgid "The ``jwk`` module offers a means to dynamically import and generate keys." msgstr "" #: ../../guide/jwk.rst:408 msgid "Import keys" msgstr "" #: ../../guide/jwk.rst:412 msgid "" "The :meth:`import_key` can choose the correct key type automatically when" " importing a JWK in dict:" msgstr "" #: ../../guide/jwk.rst:431 msgid "" "If the key is in bytes or string, not dict, developers SHOULD specify the" " key type manually:" msgstr "" #: ../../guide/jwk.rst:441 msgid "Generate keys" msgstr "" #: ../../guide/jwk.rst:445 msgid "" "The :meth:`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:456 msgid "For ``EC`` and ``OKP`` keys, the parameters are:" msgstr "" #: ../../guide/jwk.rst:466 msgid "Options" msgstr "" #: ../../guide/jwk.rst:468 msgid "" "The ``import_key`` and ``generate_key`` methods available on ``OctKey``, " "``RSAKey``, ``ECKey``, ``OKPKey``, and ``jwk`` 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:473 msgid "Some of the standard (registered) header fields are:" msgstr "" #: ../../guide/jwk.rst:475 msgid "``kty``: Key Type, it is automatically added" msgstr "" #: ../../guide/jwk.rst:476 msgid "``use``: Public Key Use, \"sig\" or \"enc\"" msgstr "" #: ../../guide/jwk.rst:477 msgid "``key_ops``: Key Operations, allowed operations of this key" msgstr "" #: ../../guide/jwk.rst:478 msgid "``alg``: Algorithm, allowed algorithm of this key" msgstr "" #: ../../guide/jwk.rst:479 msgid "``kid``: Key ID, a string of the key ID" msgstr "" #: ../../guide/jwk.rst:481 msgid "" "When using ``import_key`` and ``generate_key``, developers can pass the " "extra key ``parameters``:" msgstr "" #: ../../guide/jwk.rst:488 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:237 #, fuzzy msgid "" "``joserfc.jws`` module supports algorithms from RFC7518, RFC8037, " "RFC8812, and RFC9864. Here lists all the algorithms ``joserfc.jws`` " "supporting:" msgstr "" "``joserfc.jws`` 模块支持 RFC7518、RFC8037、 RFC8812 和 RFC9864 中的算法。" "这里列出了所有 ``joserfc.jws`` 支持的算法:" #: ../../guide/jws.rst:241 msgid "Description" msgstr "" #: ../../guide/jws.rst:243 msgid "No digital signature or MAC performed" msgstr "" #: ../../guide/jws.rst:244 msgid "HMAC using SHA-256" msgstr "" #: ../../guide/jws.rst:245 msgid "HMAC using SHA-384" msgstr "" #: ../../guide/jws.rst:246 msgid "HMAC using SHA-512" msgstr "" #: ../../guide/jws.rst:247 msgid "RSASSA-PKCS1-v1_5 using SHA-256" msgstr "" #: ../../guide/jws.rst:248 msgid "RSASSA-PKCS1-v1_5 using SHA-384" msgstr "" #: ../../guide/jws.rst:249 msgid "RSASSA-PKCS1-v1_5 using SHA-512" msgstr "" #: ../../guide/jws.rst:250 msgid "ECDSA using P-256 and SHA-256" msgstr "" #: ../../guide/jws.rst:251 msgid "ECDSA using P-384 and SHA-384" msgstr "" #: ../../guide/jws.rst:252 msgid "ECDSA using P-521 and SHA-512" msgstr "" #: ../../guide/jws.rst:253 msgid "RSASSA-PSS using SHA-256 and MGF1 with SHA-256" msgstr "" #: ../../guide/jws.rst:254 msgid "RSASSA-PSS using SHA-384 and MGF1 with SHA-384" msgstr "" #: ../../guide/jws.rst:255 msgid "RSASSA-PSS using SHA-512 and MGF1 with SHA-512" msgstr "" #: ../../guide/jws.rst:256 msgid "Edwards-curve Digital Signature" msgstr "" #: ../../guide/jws.rst:257 msgid "ECDSA using secp256k1 curve and SHA-256" msgstr "" #: ../../guide/jws.rst:258 msgid "EdDSA using the Ed25519 parameter set" msgstr "" #: ../../guide/jws.rst:259 msgid "EdDSA using the Ed448 parameter set" msgstr "" #: ../../guide/jws.rst:266 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 an :ref:`UnsupportedAlgorithmError` error." msgstr "" #: ../../guide/jws.rst:275 msgid "Unencoded Payload Option" msgstr "" #: ../../guide/jws.rst:277 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:280 msgid "" "To enable this option, you need to set the ``b64`` header parameter to " "``false`` in the JWS header." msgstr "" #: ../../guide/jws.rst:283 msgid "Here are examples demonstrating the usage of the ``b64`` option:" msgstr "" #: ../../guide/jws.rst:298 msgid "" "The ``crit`` MUST be present with ``\"b64\"`` in its value set when " "``b64`` is in the header." msgstr "" #: ../../guide/jws.rst:301 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:317 msgid "" "You can also use ``b64`` header for JSON serialization: " "``serialize_json`` and ``deserialize_json``." msgstr "" #: ../../guide/jws.rst:321 msgid "Guess Algorithms via Key" msgstr "通过密钥猜测算法" #: ../../guide/jws.rst:323 msgid "" "If you are unsure which algorithm to use but already have a key, you can " "call the :meth:`jws.JWSRegistry.guess_algorithm` method to determine a " "suitable algorithm:" 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 "Prevent sensitive data leaks" msgstr "" #: ../../guide/jwt.rst:49 msgid "" "Before calling :meth:`encode` on your claims, it's a good practice to " "ensure they do not contain sensitive information, such as credit card " "numbers." msgstr "" #: ../../guide/jwt.rst:52 msgid "" "You can use :meth:`check_sensitive_data` to detect whether sensitive data" " is present in the claims:" msgstr "" #: ../../guide/jwt.rst:62 msgid "Convert datetime" msgstr "" #: ../../guide/jwt.rst:64 msgid "" ":meth:`encode` will convert ``datetime`` to timestamp integer for " "``iat``, ``exp``, and ``nbf``:" msgstr "" #: ../../guide/jwt.rst:84 msgid "Decode token" msgstr "" #: ../../guide/jwt.rst:86 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:99 msgid "Validate claims" msgstr "" #: ../../guide/jwt.rst:101 msgid "" "The ``jwt.decode`` method will only verify if the payload is a JSON " "base64 string." msgstr "" #: ../../guide/jwt.rst:104 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:129 msgid "The Individual Claims Requests JSON object contains:" msgstr "" #: ../../guide/jwt.rst:131 msgid "``essential``" msgstr "``essential``" #: ../../guide/jwt.rst:132 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:135 msgid "``value``" msgstr "``value``" #: ../../guide/jwt.rst:136 msgid "OPTIONAL. Requests that the Claim be returned with a particular value." msgstr "" #: ../../guide/jwt.rst:138 msgid "``values``" msgstr "``values``" #: ../../guide/jwt.rst:139 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:142 msgid "And we added one more field:" msgstr "" #: ../../guide/jwt.rst:144 msgid "``allow_blank``" msgstr "``allow_blank``" #: ../../guide/jwt.rst:145 msgid "OPTIONAL. Allow essential claims to be an empty string." msgstr "" #: ../../guide/jwt.rst:148 msgid "Missing essential claims" msgstr "" #: ../../guide/jwt.rst:163 msgid "Allow empty essential claims" msgstr "" #: ../../guide/jwt.rst:174 msgid "Invalid claims values" msgstr "" #: ../../guide/jwt.rst:184 msgid "Default validators" msgstr "" #: ../../guide/jwt.rst:186 msgid "" "The ``JWTClaimsRegistry`` has built-in validators for timing related " "fields:" msgstr "" #: ../../guide/jwt.rst:188 msgid "``exp``: expiration time" msgstr "" #: ../../guide/jwt.rst:189 msgid "``nbf``: not before" msgstr "" #: ../../guide/jwt.rst:190 msgid "``iat``: issued at" msgstr "" #: ../../guide/jwt.rst:199 msgid "List validation" msgstr "" #: ../../guide/jwt.rst:201 msgid "" "When validating claims that contain lists, the registry checks if **any**" " of the required values are present in the claim's list. This behavior is" " designed for flexible authorization checks where matching any of the " "required permissions grants access. For single values, it checks for an " "exact match." msgstr "" #: ../../guide/jwt.rst:206 msgid "" "This is particularly useful for validating role based or permission based" " claims. For example:" msgstr "" #: ../../guide/jwt.rst:226 msgid "You can also validate against a single required value:" msgstr "" #: ../../guide/jwt.rst:240 msgid "Custom validation" msgstr "" #: ../../guide/jwt.rst:242 #, python-brace-format msgid "" "When it's not possible to validate a claim using ``ClaimsOption``, you " "can define a custom validation method named ``validate_{name}``. For " "example, if the claims must include a ``source`` field, and the value of " "``source`` must be an HTTPS URL, you can implement a custom method to " "enforce this requirement." msgstr "" #: ../../guide/jwt.rst:258 msgid "Then, you can validate the claims with:" msgstr "" #: ../../guide/jwt.rst:265 msgid "JWS & JWE" msgstr "" #: ../../guide/jwt.rst:267 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:282 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:293 msgid "Another difference is the key used for ``encode`` and ``decode``." msgstr "" #: ../../guide/jwt.rst:295 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:299 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:304 msgid "The key parameter" msgstr "" #: ../../guide/jwt.rst:306 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:310 msgid "Key types" msgstr "" #: ../../guide/jwt.rst:312 msgid "" "Each algorithm (``alg`` in header) requires a certain type of key. For " "example:" msgstr "" #: ../../guide/jwt.rst:314 msgid "``HS256`` requires ``OctKey``" msgstr "" #: ../../guide/jwt.rst:315 msgid "``RS256`` requires ``RSAKey``" msgstr "" #: ../../guide/jwt.rst:316 msgid "``ES256`` requires ``ECKey`` or ``OKPKey``" msgstr "" #: ../../guide/jwt.rst:318 msgid "You can find the correct key type for each algorithm at:" msgstr "" #: ../../guide/jwt.rst:320 ../../guide/jwt.rst:480 msgid ":ref:`JSON Web Signature Algorithms `" msgstr "" #: ../../guide/jwt.rst:321 ../../guide/jwt.rst:481 msgid ":ref:`JSON Web Encryption Algorithms `" msgstr "" #: ../../guide/jwt.rst:323 msgid "Here is an example of a JWT with \"alg\" of ``RS256`` in JWS type:" msgstr "" #: ../../guide/jwt.rst:342 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:347 msgid "Use key set" msgstr "" #: ../../guide/jwt.rst:349 msgid "" "You can also pass a JWK Set to the ``key`` parameter of :meth:`encode` " "and :meth:`decode` methods." msgstr "" #: ../../guide/jwt.rst:366 msgid "" "The methods will find the correct key according to the ``kid`` you " "specified. If there is no ``kid`` in header, it will pick one randomly " "and add the ``kid`` of the key into header." msgstr "" #: ../../guide/jwt.rst:370 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:387 msgid "Callable key" msgstr "" #: ../../guide/jwt.rst:389 msgid "It is also possible to assign a callable function as the ``key``:" msgstr "" #: ../../guide/jwt.rst:408 msgid "Embedded JWK" msgstr "" #: ../../guide/jwt.rst:410 msgid "" "The key may be embedded directly in the token's header. For example, the " "decoded header might look like this:" msgstr "" #: ../../guide/jwt.rst:425 msgid "" "In such cases, you don't need to supply a separate key manually. Instead," " as shown above, you can use a callable key function to dynamically " "resolve the embedded JWK value." msgstr "" #: ../../guide/jwt.rst:440 msgid "Embedded JWK Set URL" msgstr "" #: ../../guide/jwt.rst:442 msgid "" "As shown above, the key may also be provided as a JWK Set URL within the " "token header, for example:" msgstr "" #: ../../guide/jwt.rst:452 msgid "In this case, you can use a callable key function to import the JWKs:" msgstr "" #: ../../guide/jwt.rst:469 msgid "Use a cache method to improve the performance." msgstr "" #: ../../guide/jwt.rst:474 msgid "" "The :meth:`encode` and :meth:`decode` accept an ``algorithms`` parameter " "for specifying the allowed algorithms. By default, it only allows you to " "use the **recommended** algorithms." msgstr "" #: ../../guide/jwt.rst:478 msgid "You can find out the recommended algorithms at:" msgstr "" #: ../../guide/jwt.rst:483 msgid "" "For instance, ``HS384`` is not a recommended algorithm, and you want to " "use this algorithm:" msgstr "" #: ../../guide/jwt.rst:494 msgid "" "If not specifying the ``algorithms`` parameter, the ``encode`` method " "will raise an error." msgstr "" #: ../../guide/jwt.rst:498 msgid "JSON Encoder and Decoder" msgstr "JSON 的编码与解码" #: ../../guide/jwt.rst:502 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:505 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:538 msgid "" "To resolve this issue, you can pass a custom ``JSONEncoder`` using the " "``encoder_cls`` parameter." msgstr "" #: ../../guide/jwt.rst:556 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 "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/index.po000066400000000000000000000124561511744432500234550ustar00rootroot00000000000000# 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-12-09 14:11+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" #: ../../index.rst:69 msgid "Getting started" msgstr "开始上手" #: ../../index.rst:76 msgid "Essentials" msgstr "必读文档" #: ../../index.rst:86 msgid "Recipes" msgstr "小技巧" #: ../../index.rst:94 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:31 msgid "You would find more details and advanced usage in :ref:`jwt` section." msgstr "您可以在 :ref:`jwt` 部分找到更多详细信息和高级用法。" #: ../../index.rst:35 msgid "" "The string ``\"your-secret-key\"`` 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 "上述示例中使用的字符串 ``\"your-secret-key\"`` 仅用于演示目的。在生产环境中,使用高度安全的密钥以确保强大的安全措施是至关重要的。" #: ../../index.rst:40 msgid "RFCs" msgstr "RFCs" #: ../../index.rst:42 msgid "It follows RFCs with extensible API. The module has implementations of:" msgstr "它遵循可扩展的 API,并遵循 RFC 标准。该模块包含以下实现:" #: ../../index.rst:44 msgid ":ref:`rfc7515`: :ref:`JSON Web Signature `" msgstr ":ref:`rfc7515`: :ref:`JSON Web Signature `" #: ../../index.rst:45 msgid ":ref:`rfc7516`: :ref:`JSON Web Encryption `" msgstr ":ref:`rfc7516`: :ref:`JSON Web Encryption `" #: ../../index.rst:46 msgid ":ref:`rfc7517`: :ref:`JSON Web Key `" msgstr ":ref:`rfc7517`: :ref:`JSON Web Key `" #: ../../index.rst:47 msgid ":ref:`rfc7518`: :ref:`JSON Web Algorithms `" msgstr ":ref:`rfc7518`: :ref:`JSON Web Algorithms `" #: ../../index.rst:48 msgid ":ref:`rfc7519`: :ref:`JSON Web Token `" msgstr ":ref:`rfc7519`: :ref:`JSON Web Token `" #: ../../index.rst:49 msgid "" ":ref:`rfc7520`: Examples of Protecting Content Using JSON Object Signing " "and Encryption" msgstr ":ref:`rfc7520`: 使用 JSON 对象签名和加密保护内容的示例" #: ../../index.rst:50 msgid ":ref:`rfc7638`: ``thumbprint`` for JWK" msgstr ":ref:`rfc7638`: JWK 的 ``thumbprint``" #: ../../index.rst:51 msgid "" ":ref:`rfc7797`: JSON Web Signature (JWS) :ref:`Unencoded Payload Option " "`" msgstr ":ref:`rfc7797`: JSON Web Signature (JWS) :ref:`未编码载荷选项 `" #: ../../index.rst:52 msgid ":ref:`rfc8037`: :ref:`OKPKey` and ``EdDSA`` algorithm" msgstr ":ref:`rfc8037`: :ref:`OKPKey` 和 ``EdDSA`` 算法" #: ../../index.rst:53 msgid "" ":ref:`rfc8812`: ``ES256K`` algorithm and ``ECKey`` with curve " "``secp256k1``" msgstr ":ref:`rfc8812`: ``ES256K`` 算法和使用 ``secp256k1`` 曲线的 ``ECKey``" #: ../../index.rst:54 msgid ":ref:`rfc9278`: JWK Thumbprint URI (``thumbprint_uri``)" msgstr ":ref:`rfc9278`: JWK 的 ``thumbprint_uri``" #: ../../index.rst:55 msgid ":ref:`rfc9864`: ``Ed25519`` and ``Ed448`` algorithms" msgstr ":ref:`rfc9864`: ``Ed25519`` 和 ``Ed448`` 算法" #: ../../index.rst:57 msgid "And draft RFCs implementation of:" msgstr "同时包含如下 JOSE 草案的实现:" #: ../../index.rst:59 msgid ":ref:`chacha20`" msgstr ":ref:`chacha20`" #: ../../index.rst:60 msgid ":ref:`ecdh1pu`" msgstr ":ref:`ecdh1pu`" #: ../../index.rst:62 msgid "RFC7520 is implemented as test cases." msgstr "RFC7520 是测试案例,详情请参考源码里的 tests 部分。" #: ../../index.rst:65 msgid "Next" msgstr "继续阅读" #: ../../index.rst:67 msgid "" "Explore the following sections to discover more about ``joserfc`` and its" " features." msgstr "浏览以下部分,了解更多关于 ``joserfc`` 及其特性的内容。" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/install.po000066400000000000000000000067121511744432500240120ustar00rootroot00000000000000# 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-08-26 11:26+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 "Package install" msgstr "安装" #: ../../install.rst:22 msgid "" "``joserfc`` is conveniently available as a Python package on PyPI_ and " "conda-forge_. It can be easily installed via:" msgstr "" "``joserfc`` 可作为 Python 包方便地在 PyPI_ 和 conda-forge_ 上获取。可以通过以下方式轻松安装:" #: ../../install.rst msgid ":iconify:`devicon:pypi` pip" msgstr ":iconify:`devicon:pypi` pip" #: ../../install.rst msgid ":iconify:`material-icon-theme:uv` uv" msgstr ":iconify:`material-icon-theme:uv` uv" #: ../../install.rst msgid ":iconify:`devicon:anaconda` conda" msgstr ":iconify:`devicon:anaconda` conda" #: ../../install.rst:48 msgid "" "To use :ref:`chacha20` algorithms, developers have to install the " "``PyCryptodome`` module." msgstr "要使用 :ref:`chacha20` 算法,开发人员必须安装 ``PyCryptodome`` 模块。" #: ../../install.rst:58 msgid "Dependency management" msgstr "依赖管理" #: ../../install.rst:60 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:64 ../../install.rst:69 msgid "pyproject.toml" msgstr "pyproject.toml" #: ../../install.rst:66 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:78 ../../install.rst:83 msgid "requirements.txt" msgstr "requirements.txt" #: ../../install.rst:80 msgid "" "If you're tracking dependencies in ``requirements.txt``, you can add " "``joserfc`` to the requirements file." msgstr "" "如果您在 ``requirements.txt`` 中跟踪依赖项,您可以将 ``joserfc`` 添加到 " "``requirements.txt`` 文件中。" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/migrations.po000066400000000000000000000373411511744432500245220ustar00rootroot00000000000000# 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-12-09 14:11+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 "JWT" #: ../../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 "jwt.encode" #: ../../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:81 #: ../../migrations/authlib.rst:97 msgid "Authlib" msgstr "Authlib" #: ../../migrations/authlib.rst:30 ../../migrations/authlib.rst:86 #: ../../migrations/authlib.rst:109 ../../migrations/index.rst:23 #: ../../migrations/pyjwt.rst:23 ../../migrations/pyjwt.rst:45 #: ../../migrations/pyjwt.rst:64 ../../migrations/pyjwt.rst:96 #: ../../migrations/pyjwt.rst:136 ../../migrations/pyjwt.rst:155 #: ../../migrations/python-jose.rst:31 ../../migrations/python-jose.rst:72 #: ../../migrations/python-jose.rst:101 ../../migrations/python-jose.rst:122 #: ../../migrations/python-jose.rst:137 ../../migrations/python-jose.rst:155 msgid "joserfc" msgstr "joserfc" #: ../../migrations/authlib.rst:40 ../../migrations/pyjwt.rst:34 msgid "jwt.decode" msgstr "jwt.decode" #: ../../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:149 msgid "JWK" msgstr "" #: ../../migrations/authlib.rst:77 msgid "" "When using methods such as ``.as_dict``, ``.as_bytes``, ``.as_pem``, and " "others, ``joserfc`` uses the parameter name ``private``, whereas " "``authlib.jose`` uses ``is_private``:" msgstr "" #: ../../migrations/authlib.rst:92 ../../migrations/python-jose.rst:14 msgid "JWS" msgstr "JWS" #: ../../migrations/authlib.rst:94 msgid "" "When migrating JWS (JSON Web Signature) operations from Authlib to " "``joserfc``, follow these steps:" msgstr "" #: ../../migrations/authlib.rst:121 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:126 ../../migrations/python-jose.rst:55 msgid "JWE" msgstr "JWE" #: ../../migrations/authlib.rst:128 msgid "" "The method names for JWE serialization and deserialization are different " "between Authlib and ``joserfc``." msgstr "" #: ../../migrations/authlib.rst:131 msgid "In Authlib, the methods for JWE serialization and deserialization are:" msgstr "" #: ../../migrations/authlib.rst:133 msgid "``.serialize_compact(header, payload, key)``" msgstr "``.serialize_compact(header, payload, key)``" #: ../../migrations/authlib.rst:134 msgid "``.deserialize_compact(token, key)``" msgstr "``.deserialize_compact(token, key)``" #: ../../migrations/authlib.rst:144 msgid "" "In ``joserfc``, the equivalent methods for JWE serialization and " "deserialization are:" msgstr "" #: ../../migrations/authlib.rst:146 msgid "``.encrypt_compact(header, payload, key)``" msgstr "``.encrypt_compact(header, payload, key)``" #: ../../migrations/authlib.rst:147 msgid "``.decrypt_compact(token, key)``" msgstr "``.decrypt_compact(token, key)``" #: ../../migrations/authlib.rst:156 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/index.rst:15 msgid "Comparison" msgstr "对比" #: ../../migrations/index.rst:17 msgid "" "joserfc is the most feature-complete Python library for the JOSE " "specifications, providing full coverage of all relevant RFCs." msgstr "" #: ../../migrations/index.rst:20 msgid "" "The following highlights the key differences between joserfc and other " "Python libraries:" msgstr "" #: ../../migrations/index.rst:23 msgid "Features" msgstr "" #: ../../migrations/index.rst:23 msgid "authlib.jose" msgstr "authlib.jose" #: ../../migrations/index.rst:23 msgid "pyjwt" msgstr "pyjwt" #: ../../migrations/index.rst:23 ../../migrations/python-jose.rst:22 #: ../../migrations/python-jose.rst:63 ../../migrations/python-jose.rst:92 msgid "python-jose" msgstr "python-jose" #: ../../migrations/index.rst:23 msgid "jwcrypto" msgstr "jwcrypto" #: ../../migrations/index.rst:25 msgid "Type Hints" msgstr "" #: ../../migrations/index.rst:25 ../../migrations/index.rst:26 #: ../../migrations/index.rst:27 ../../migrations/index.rst:28 #: ../../migrations/index.rst:29 ../../migrations/index.rst:30 #: ../../migrations/index.rst:31 ../../migrations/index.rst:32 #: ../../migrations/index.rst:33 ../../migrations/index.rst:34 #: ../../migrations/index.rst:35 ../../migrations/index.rst:36 #: ../../migrations/index.rst:37 ../../migrations/index.rst:38 #: ../../migrations/index.rst:39 msgid ":bdg-success:`Yes`" msgstr ":bdg-success:`是`" #: ../../migrations/index.rst:25 ../../migrations/index.rst:27 #: ../../migrations/index.rst:28 ../../migrations/index.rst:29 #: ../../migrations/index.rst:30 ../../migrations/index.rst:31 #: ../../migrations/index.rst:33 ../../migrations/index.rst:34 #: ../../migrations/index.rst:35 ../../migrations/index.rst:36 #: ../../migrations/index.rst:37 ../../migrations/index.rst:38 #: ../../migrations/index.rst:39 msgid ":bdg-danger:`No`" msgstr ":bdg-danger:`否`" #: ../../migrations/index.rst:26 msgid "Compact JWS" msgstr "" #: ../../migrations/index.rst:27 msgid "JSON JWS" msgstr "" #: ../../migrations/index.rst:28 msgid "Compact JWE" msgstr "" #: ../../migrations/index.rst:29 msgid "JSON JWE" msgstr "" #: ../../migrations/index.rst:30 msgid "Key generation" msgstr "" #: ../../migrations/index.rst:30 ../../migrations/index.rst:31 #, fuzzy msgid ":bdg-danger:`Yes`" msgstr ":bdg-danger:`否`" #: ../../migrations/index.rst:31 msgid "Key importing" msgstr "" #: ../../migrations/index.rst:32 msgid "JWT on JWS" msgstr "" #: ../../migrations/index.rst:33 msgid "JWT on JWE" msgstr "" #: ../../migrations/index.rst:34 msgid "RFC7638" msgstr "" #: ../../migrations/index.rst:35 msgid "RFC7797" msgstr "" #: ../../migrations/index.rst:36 msgid "RFC8037" msgstr "" #: ../../migrations/index.rst:37 msgid "RFC8812" msgstr "" #: ../../migrations/index.rst:38 msgid "RFC9278" msgstr "" #: ../../migrations/index.rst:39 msgid "RFC9864" msgstr "" #: ../../migrations/index.rst:39 msgid "Not released" 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:84 ../../migrations/pyjwt.rst:130 #: ../../migrations/pyjwt.rst:149 msgid "PyJWT" msgstr "PyJWT" #: ../../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 ../../migrations/python-jose.rst:115 msgid "``get_unverified_header``" msgstr "``get_unverified_header``" #: ../../migrations/pyjwt.rst:59 msgid "" "PyJWT module provides a method called ``get_unverified_header``, which " "allows extracting the header from a JWT without verifying its signature." msgstr "" #: ../../migrations/pyjwt.rst:62 ../../migrations/python-jose.rst:120 msgid "In ``joserfc``, we can get the unverified header with:" msgstr "" #: ../../migrations/pyjwt.rst:76 msgid "Non-plain string key" msgstr "" #: ../../migrations/pyjwt.rst:78 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:82 msgid "Let's take an example using an RSA key:" msgstr "" #: ../../migrations/pyjwt.rst:112 msgid "Claims validation" msgstr "" #: ../../migrations/pyjwt.rst:114 msgid "" "Both PyJWT and ``joserfc`` provide mechanisms for claims validation, " "although they differ in their approach." msgstr "" #: ../../migrations/pyjwt.rst:117 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:122 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:128 msgid "Verify \"exp\"" msgstr "" #: ../../migrations/pyjwt.rst:147 msgid "Required claims" msgstr "" #: ../../migrations/pyjwt.rst:168 msgid "" "The ``JWTClaimsRegistry`` accepts each claim as an `Individual Claims " "Requests`_ JSON object. You can learn more from :ref:`claims`." msgstr "" #: ../../migrations/pyjwt.rst:174 #, fuzzy msgid "PyJWKClient" msgstr "PyJWT" #: ../../migrations/pyjwt.rst:176 msgid "" "Unlike ``PyJWT``, ``joserfc`` does not provide a built-in HTTP client for" " fetching JWK Sets (jwks) from a URL. You are free to use any HTTP " "library you prefer to retrieve the JWK Set." msgstr "" #: ../../migrations/pyjwt.rst:179 msgid "Here is an example of using the **requests** library to fetch a JWK Set." msgstr "" #: ../../migrations/pyjwt.rst:196 msgid "Here is a real-world example demonstrating how to fetch :ref:`azure`." msgstr "" #: ../../migrations/python-jose.rst:2 msgid "Migrating from python-jose" msgstr "从 python-jose 迁移到 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: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: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:133 msgid "``get_unverified_claims``" msgstr "``get_unverified_claims``" #: ../../migrations/python-jose.rst:135 msgid "" "You can also use the ``jws.extract_compact`` method to extract the JWT's " "claims:" msgstr "" #: ../../migrations/python-jose.rst:151 msgid "" "In ``python-jose``, you can use ``jwk.construct`` to create a key " "instance from a JWK-formatted dictionary. In contrast, ``joserfc`` " "provides the ``jwk.import_key`` method to achieve the same result." msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/recipes.po000066400000000000000000000166451511744432500240040ustar00rootroot00000000000000# 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-07-14 11:51+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" #: ../../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 https://learn.microsoft.com/en-" "us/azure/active-directory/develop/access-tokens:" msgstr "" #: ../../recipes/azure.rst:29 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:55 msgid "Steps for decoding" msgstr "" #: ../../recipes/azure.rst:57 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:62 msgid "OpenID Configuration Endpoint" msgstr "" #: ../../recipes/azure.rst:64 msgid "" "You can obtain the OpenID configuration endpoint from Microsoft by " "forming a URL in the following format:" msgstr "" #: ../../recipes/azure.rst:71 #, python-brace-format 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:81 msgid "JWK Set URI" msgstr "" #: ../../recipes/azure.rst:83 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:91 #, python-brace-format 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:99 msgid "Validating JWT Tokens" msgstr "" #: ../../recipes/azure.rst:101 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:105 msgid "Using a Callable Key" msgstr "" #: ../../recipes/azure.rst:107 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:137 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:142 msgid "Let's enhance the callable key method to improve its efficiency." msgstr "" #: ../../recipes/azure.rst:162 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:170 msgid "Manual Token Decoding" msgstr "" #: ../../recipes/azure.rst:172 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:185 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:202 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:`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 "" #~ 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 "" #~ 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 "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/rfc.po000066400000000000000000001101751511744432500231150ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2025. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc 1.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-12-09 14:11+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" #: ../../rfc/7515.rst:4 msgid "RFC 7515" msgstr "" #: ../../rfc/7515.rst:6 msgid "" "RFC7515 defines JSON Web Signature (JWS), a specification for " "representing digitally signed or MAC-protected content using JSON-based " "data structures." msgstr "" #: ../../rfc/7515.rst:11 ../../rfc/7516.rst:13 ../../rfc/7517.rst:12 #: ../../rfc/7518.rst:13 ../../rfc/7519.rst:15 ../../rfc/7520.rst:19 #: ../../rfc/8037.rst:16 ../../rfc/9278.rst:12 msgid "Definition" msgstr "" #: ../../rfc/7515.rst:13 msgid "" "RFC 7515 specifies the complete framework for creating and validating " "JSON Web Signatures. The specification includes:" msgstr "" #: ../../rfc/7515.rst:17 msgid "JWS Header" msgstr "" #: ../../rfc/7515.rst:19 msgid "" "The JWS Header is a set of metadata that describes how the JWS object is " "constructed and verified. RFC 7515 defines a registry of header " "parameters, including:" msgstr "" #: ../../rfc/7515.rst:23 msgid "``alg`` — the algorithm used to generate the signature or MAC (required)" msgstr "" #: ../../rfc/7515.rst:24 ../../rfc/7516.rst:27 msgid "``jwk`` / ``jku`` — a JSON Web Key or URL pointing to a key set" msgstr "" #: ../../rfc/7515.rst:25 msgid "``kid`` — a key identifier that helps recipients select the correct key" msgstr "" #: ../../rfc/7515.rst:26 ../../rfc/7516.rst:31 msgid "``typ`` and ``cty`` — type and content-type hints" msgstr "" #: ../../rfc/7515.rst:27 msgid "" "``crit`` — a list of critical header parameters that must be understood " "by the verifier" msgstr "" #: ../../rfc/7515.rst:30 msgid "" "The header **must** be encoded as a Base64URL-encoded JSON object when " "used in compact serialization, and may appear as either a protected or " "unprotected header in JSON serialization." msgstr "" #: ../../rfc/7515.rst:35 ../../rfc/7515.rst:85 ../../rfc/7516.rst:41 msgid "Compact Serialization" msgstr "" #: ../../rfc/7515.rst:37 msgid "" "The compact serialization format represents a JWS as a single, period-" "separated string consisting of three parts:" msgstr "" #: ../../rfc/7515.rst:44 msgid "Each part is Base64URL-encoded. This format is designed to be:" msgstr "" #: ../../rfc/7515.rst:46 msgid "minimal and URL-safe" msgstr "" #: ../../rfc/7515.rst:47 msgid "suitable for HTTP headers, query parameters, and tokens such as JWT" msgstr "" #: ../../rfc/7515.rst:49 msgid "" "Compact serialization supports exactly **one signature**, with no " "unprotected headers." msgstr "" #: ../../rfc/7515.rst:52 ../../rfc/7515.rst:86 ../../rfc/7516.rst:62 msgid "JSON Serialization" msgstr "" #: ../../rfc/7515.rst:54 msgid "" "JSON serialization uses a structured JSON object to represent a JWS. It " "is designed for use cases that require more flexibility than the compact " "form. A JSON-serialized JWS can include:" msgstr "" #: ../../rfc/7515.rst:58 msgid "**multiple signatures** over the same payload" msgstr "" #: ../../rfc/7515.rst:59 msgid "both **protected** and **unprotected** headers" msgstr "" #: ../../rfc/7515.rst:60 msgid "richer metadata, such as per-signature header values" msgstr "" #: ../../rfc/7515.rst:62 msgid "The primary fields defined in RFC 7515 are:" msgstr "" #: ../../rfc/7515.rst:64 msgid "``payload`` — the Base64URL-encoded content" msgstr "" #: ../../rfc/7515.rst:65 msgid "" "``signatures`` — an array of signature objects, each containing: - " "``protected`` — a Base64URL-encoded header - ``header`` — an unprotected " "header (optional) - ``signature`` — the Base64URL-encoded signature value" msgstr "" #: ../../rfc/7515.rst:70 msgid "" "JSON serialization is not intended for compact transmission but is ideal " "for multi-party signing, debugging, and more expressive JOSE workflows." msgstr "" #: ../../rfc/7515.rst:74 ../../rfc/7516.rst:87 ../../rfc/7517.rst:64 #: ../../rfc/7518.rst:134 ../../rfc/7519.rst:80 ../../rfc/7520.rst:34 #: ../../rfc/7638.rst:34 ../../rfc/7797.rst:37 ../../rfc/8037.rst:28 #: ../../rfc/8812.rst:18 ../../rfc/9278.rst:26 ../../rfc/9864.rst:12 msgid "Implementation" msgstr "" #: ../../rfc/7515.rst:76 msgid "All features defined in RFC 7515 are fully implemented in ``joserfc``." msgstr "" #: ../../rfc/7515.rst:79 ../../rfc/7516.rst:92 ../../rfc/7517.rst:69 #: ../../rfc/7518.rst:140 ../../rfc/7519.rst:85 ../../rfc/8037.rst:37 msgid "Private modules" msgstr "" #: ../../rfc/7515.rst:81 msgid "The source code resides in the private module ``joserfc/_rfc7515``:" msgstr "" #: ../../rfc/7515.rst:83 msgid "JWS algorithms base models" msgstr "" #: ../../rfc/7515.rst:84 msgid "JWS algorithm registry" msgstr "" #: ../../rfc/7515.rst:89 ../../rfc/7516.rst:102 ../../rfc/7517.rst:78 #: ../../rfc/7519.rst:88 ../../rfc/8037.rst:42 msgid "Public exports" msgstr "" #: ../../rfc/7515.rst:91 msgid "" "You should always interact with JWS functionality via ``joserfc.jws``, " "rather than importing from the private module directly." msgstr "" #: ../../rfc/7515.rst:94 msgid "" ":meth:`joserfc.jws.serialize_compact`: Construct JWS compact " "serialization." msgstr "" #: ../../rfc/7515.rst:95 msgid ":meth:`joserfc.jws.deserialize_compact`: Parse JWS compact serialization." msgstr "" #: ../../rfc/7515.rst:96 msgid ":meth:`joserfc.jws.serialize_json`: Construct JWS JSON serialization." msgstr "" #: ../../rfc/7515.rst:97 msgid ":meth:`joserfc.jws.deserialize_json`: Parse JWS JSON serialization." msgstr "" #: ../../rfc/7516.rst:4 msgid "RFC 7516" msgstr "" #: ../../rfc/7516.rst:6 msgid "" "RFC7516 defines JSON Web Encryption (JWE), a specification for " "representing encrypted content using JSON-based data structures. It " "describes how to encrypt arbitrary payloads, including how keys are " "managed, how encryption parameters are represented, and how JWE objects " "are serialized." msgstr "" #: ../../rfc/7516.rst:15 msgid "" "RFC 7516 specifies the complete framework for creating and decrypting " "JSON Web Encryption (JWE) objects. The specification defines:" msgstr "" #: ../../rfc/7516.rst:19 msgid "JWE Header" msgstr "" #: ../../rfc/7516.rst:21 msgid "" "The JWE Header is a JSON object that specifies metadata describing how " "the payload is encrypted and how the Content Encryption Key (CEK) is " "managed. RFC 7516 defines a registry of header parameters, including:" msgstr "" #: ../../rfc/7516.rst:25 msgid "" "``alg`` — the key management algorithm used to encrypt or wrap the CEK " "(required)" msgstr "" #: ../../rfc/7516.rst:26 msgid "" "``enc`` — the content encryption algorithm used to protect the payload " "(required)" msgstr "" #: ../../rfc/7516.rst:28 msgid "``kid`` — a key identifier to help recipients locate the correct key" msgstr "" #: ../../rfc/7516.rst:29 msgid "``zip`` — indicates that the plaintext is compressed before encryption" msgstr "" #: ../../rfc/7516.rst:30 msgid "``apu`` / ``apv`` — agreement PartyU/PartyV info for ECDH-based algorithms" msgstr "" #: ../../rfc/7516.rst:32 msgid "``crit`` — critical header parameters that must be understood" msgstr "" #: ../../rfc/7516.rst:34 msgid "" "In the compact serialization format, the header is Base64URL-encoded as " "the first segment of the JWE." msgstr "" #: ../../rfc/7516.rst:37 msgid "" "In JSON serialization, it may appear as either a protected or unprotected" " header, similar to JWS." msgstr "" #: ../../rfc/7516.rst:43 msgid "" "The compact serialization format represents a JWE as a single, period-" "separated string consisting of **five** Base64URL-encoded parts:" msgstr "" #: ../../rfc/7516.rst:50 msgid "Each part has a specific meaning:" msgstr "" #: ../../rfc/7516.rst:52 msgid "``protected-header`` — metadata describing algorithms and parameters" msgstr "" #: ../../rfc/7516.rst:53 msgid "``encrypted-key`` — the CEK encrypted or wrapped with the recipient's key" msgstr "" #: ../../rfc/7516.rst:54 msgid "``iv`` — initialization vector for the content encryption algorithm" msgstr "" #: ../../rfc/7516.rst:55 ../../rfc/7516.rst:76 msgid "``ciphertext`` — the encrypted payload" msgstr "" #: ../../rfc/7516.rst:56 msgid "``tag`` — the authentication tag for AEAD algorithms" msgstr "" #: ../../rfc/7516.rst:58 msgid "" "Compact serialization is designed for simple and compact transmission and" " supports exactly **one recipient**." msgstr "" #: ../../rfc/7516.rst:64 msgid "" "The JSON serialization format expresses a JWE as a JSON object and " "supports more complex use cases than compact serialization. It is used " "when:" msgstr "" #: ../../rfc/7516.rst:67 msgid "encrypting for **multiple recipients**" msgstr "" #: ../../rfc/7516.rst:68 msgid "including per-recipient header parameters" msgstr "" #: ../../rfc/7516.rst:69 msgid "debugging or inspecting encryption metadata" msgstr "" #: ../../rfc/7516.rst:71 msgid "A JSON-serialized JWE typically includes the following fields:" msgstr "" #: ../../rfc/7516.rst:73 msgid "``protected`` — a Base64URL-encoded header shared across recipients" msgstr "" #: ../../rfc/7516.rst:74 msgid "``unprotected`` — an unprotected shared header (optional)" msgstr "" #: ../../rfc/7516.rst:75 msgid "``iv`` — the initialization vector" msgstr "" #: ../../rfc/7516.rst:77 msgid "``tag`` — the authentication tag" msgstr "" #: ../../rfc/7516.rst:78 msgid "``recipients`` — an array of recipient objects, each containing:" msgstr "" #: ../../rfc/7516.rst:80 msgid "``header`` — per-recipient unprotected header (optional)" msgstr "" #: ../../rfc/7516.rst:81 msgid "``encrypted_key`` — a CEK encrypted for that recipient" msgstr "" #: ../../rfc/7516.rst:83 msgid "" "JSON serialization enables multi-recipient encrypted messages and greater" " flexibility than the compact format." msgstr "" #: ../../rfc/7516.rst:89 msgid "All features defined in RFC 7516 are fully implemented in ``joserfc``." msgstr "" #: ../../rfc/7516.rst:94 msgid "" "The underlying implementation can be found in the private module " "``joserfc/_rfc7516``:" msgstr "" #: ../../rfc/7516.rst:97 msgid "JWE algorithms base models" msgstr "" #: ../../rfc/7516.rst:98 msgid "Compact Serialization: encryption and decryption" msgstr "" #: ../../rfc/7516.rst:99 msgid "JSON Serialization: encryption and decryption" msgstr "" #: ../../rfc/7516.rst:104 msgid "" "These internals are re-exported through the public API in " "``joserfc.jwe``. You should always use ``joserfc.jwe`` for creating, " "encrypting, decrypting, and validating JWEs, and avoid importing from the" " private module directly." msgstr "" #: ../../rfc/7516.rst:109 msgid "" ":meth:`joserfc.jwe.encrypt_compact`: Encrypt JWE with compact " "serialization." msgstr "" #: ../../rfc/7516.rst:110 msgid "" ":meth:`joserfc.jwe.decrypt_compact`: Decrypt JWE with compact " "serialization." msgstr "" #: ../../rfc/7516.rst:111 msgid ":meth:`joserfc.jwe.encrypt_json`: Encrypt JWE with JSON serialization." msgstr "" #: ../../rfc/7516.rst:112 msgid ":meth:`joserfc.jwe.decrypt_json`: Decrypt JWE with JSON serialization." msgstr "" #: ../../rfc/7517.rst:4 msgid "RFC 7517" msgstr "" #: ../../rfc/7517.rst:6 msgid "" "RFC7517 defines the JSON Web Key (JWK) specification, a standard format " "for representing cryptographic keys using JSON. It provides a flexible " "and interoperable way to describe public keys, private keys, and " "symmetric keys for use with JWS, JWE, and other JOSE-related operations." msgstr "" #: ../../rfc/7517.rst:14 msgid "" "RFC 7517 specifies how cryptographic keys are expressed as JSON objects " "and how sets of keys are represented. The specification defines:" msgstr "" #: ../../rfc/7517.rst:18 msgid "JWK Object" msgstr "" #: ../../rfc/7517.rst:20 msgid "" "A JWK is a JSON object representing a single cryptographic key. Each JWK " "contains a set of required and optional parameters depending on the key " "type. Common parameters include:" msgstr "" #: ../../rfc/7517.rst:24 msgid "``kty`` — the key type (required), such as ``RSA``, ``EC``, or ``oct``" msgstr "" #: ../../rfc/7517.rst:25 msgid "" "``use`` — intended key usage (``sig`` for signature, ``enc`` for " "encryption)" msgstr "" #: ../../rfc/7517.rst:26 msgid "" "``key_ops`` — a list of permitted operations (``sign``, ``verify``, " "``wrapKey``, etc.)" msgstr "" #: ../../rfc/7517.rst:27 msgid "``kid`` — a key identifier for selecting a specific key" msgstr "" #: ../../rfc/7517.rst:28 msgid "``alg`` — the algorithm for which the key is intended" msgstr "" #: ../../rfc/7517.rst:29 msgid "``x5u`` / ``x5c`` / ``x5t`` — X.509 certificate chain parameters" msgstr "" #: ../../rfc/7517.rst:32 msgid "JWK Set (JWKS)" msgstr "" #: ../../rfc/7517.rst:34 msgid "" "A JWK Set is a JSON object that contains an array of JWKs. It is commonly" " used for publishing multiple keys, such as rotation sets or multi-tenant" " public keys:" msgstr "" #: ../../rfc/7517.rst:47 msgid "" "JWKS documents are frequently served over HTTPS endpoints, allowing " "clients to discover signing keys dynamically (e.g., OAuth 2.0, OpenID " "Connect)." msgstr "" #: ../../rfc/7517.rst:51 msgid "Key Usage and Operations" msgstr "" #: ../../rfc/7517.rst:53 msgid "A JWK may declare either:" msgstr "" #: ../../rfc/7517.rst:55 msgid "" "``use`` — a coarse-grained indication of intended purpose (e.g., ``sig`` " "or ``enc``), or" msgstr "" #: ../../rfc/7517.rst:57 msgid "" "``key_ops`` — a precise list of permissible operations (e.g., ``sign``, " "``verify``, ``encrypt``, ``unwrapKey``)" msgstr "" #: ../../rfc/7517.rst:60 msgid "" "These fields help recipients determine how a key should be used and " "prevent unintended or insecure key usage." msgstr "" #: ../../rfc/7517.rst:66 msgid "All definitions from RFC 7517 are fully implemented in ``joserfc``." msgstr "" #: ../../rfc/7517.rst:71 msgid "" "The underlying logic resides in the private module ``joserfc/_rfc7517``, " "which defines:" msgstr "" #: ../../rfc/7517.rst:74 msgid "Base key models" msgstr "" #: ../../rfc/7517.rst:75 msgid "Utilities for handling PEM keys" msgstr "" #: ../../rfc/7517.rst:80 msgid "" "Public classes and utilities are re-exported through ``joserfc.jwk``. You" " should always use ``joserfc.jwk`` for working with JWK objects and JWK " "Sets, rather than importing from the private module directly." msgstr "" #: ../../rfc/7517.rst:84 msgid "This includes functionality for:" msgstr "" #: ../../rfc/7517.rst:86 msgid "parsing JWK and JWKs: ``jwk.import_key``" msgstr "" #: ../../rfc/7517.rst:87 msgid "constructing JWK and JWKs: ``jwk.generate_key``" msgstr "" #: ../../rfc/7518.rst:4 msgid "RFC 7518" msgstr "" #: ../../rfc/7518.rst:6 msgid "" "RFC7518 defines the JSON Web Algorithms (JWA) specification, which lists " "the cryptographic algorithms and associated parameters used with JWS " "(JSON Web Signature), JWE (JSON Web Encryption), and JWK (JSON Web Key). " "It standardizes algorithm names, required key properties, and security " "considerations to ensure interoperable and secure JOSE implementations." msgstr "" #: ../../rfc/7518.rst:15 msgid "" "RFC 7518 provides a complete registry of algorithms used across the JOSE " "ecosystem. It defines algorithms for:" msgstr "" #: ../../rfc/7518.rst:18 msgid "digital signatures and MACs (for JWS)" msgstr "" #: ../../rfc/7518.rst:19 msgid "key management and key wrapping (for JWE)" msgstr "" #: ../../rfc/7518.rst:20 msgid "content encryption (for JWE)" msgstr "" #: ../../rfc/7518.rst:21 msgid "compression methods" msgstr "" #: ../../rfc/7518.rst:22 msgid "associated key parameters for JWK" msgstr "" #: ../../rfc/7518.rst:24 msgid "" "The specification ensures that JOSE objects can be securely encoded, " "signed, encrypted, and decrypted using consistent algorithm names and " "structures." msgstr "" #: ../../rfc/7518.rst:29 msgid "JWS Algorithms" msgstr "" #: ../../rfc/7518.rst:31 msgid "The signature and MAC algorithms defined for JWS include:" msgstr "" #: ../../rfc/7518.rst:33 msgid "**Using the Algorithm \"none\"** (not secure)" msgstr "" #: ../../rfc/7518.rst:35 msgid "**HMAC with SHA-2 functions**:" msgstr "" #: ../../rfc/7518.rst:37 msgid "``HS256``" msgstr "" #: ../../rfc/7518.rst:38 msgid "``HS384``" msgstr "" #: ../../rfc/7518.rst:39 msgid "``HS512``" msgstr "" #: ../../rfc/7518.rst:41 msgid "**RSA PKCS#1 v1.5 signatures**:" msgstr "" #: ../../rfc/7518.rst:43 msgid "``RS256``" msgstr "" #: ../../rfc/7518.rst:44 msgid "``RS384``" msgstr "" #: ../../rfc/7518.rst:45 msgid "``RS512``" msgstr "" #: ../../rfc/7518.rst:47 msgid "**RSA-PSS signatures**:" msgstr "" #: ../../rfc/7518.rst:49 msgid "``PS256``" msgstr "" #: ../../rfc/7518.rst:50 msgid "``PS384``" msgstr "" #: ../../rfc/7518.rst:51 msgid "``PS512``" msgstr "" #: ../../rfc/7518.rst:53 msgid "**Elliptic Curve signatures**:" msgstr "" #: ../../rfc/7518.rst:55 msgid "``ES256`` (P-256 + SHA-256)" msgstr "" #: ../../rfc/7518.rst:56 msgid "``ES384`` (P-384 + SHA-384)" msgstr "" #: ../../rfc/7518.rst:57 msgid "``ES512`` (P-521 + SHA-512)" msgstr "" #: ../../rfc/7518.rst:59 msgid "" "The specification defines requirements such as key sizes, curve types, " "and verification rules." msgstr "" #: ../../rfc/7518.rst:63 msgid "JWE Key Management Algorithms" msgstr "" #: ../../rfc/7518.rst:65 msgid "" "JWE uses key management algorithms to establish or wrap the Content " "Encryption Key (CEK). RFC 7518 defines:" msgstr "" #: ../../rfc/7518.rst:68 msgid "**RSA-based key management**:" msgstr "" #: ../../rfc/7518.rst:70 msgid "``RSA1_5`` (deprecated for new deployments)" msgstr "" #: ../../rfc/7518.rst:71 msgid "``RSA-OAEP``" msgstr "" #: ../../rfc/7518.rst:72 msgid "``RSA-OAEP-256``" msgstr "" #: ../../rfc/7518.rst:74 msgid "**AES Key Wrap**:" msgstr "" #: ../../rfc/7518.rst:76 msgid "``A128KW``" msgstr "" #: ../../rfc/7518.rst:77 msgid "``A192KW``" msgstr "" #: ../../rfc/7518.rst:78 msgid "``A256KW``" msgstr "" #: ../../rfc/7518.rst:80 msgid "**Elliptic Curve Diffie–Hellman**:" msgstr "" #: ../../rfc/7518.rst:82 msgid "``ECDH-ES`` (direct)" msgstr "" #: ../../rfc/7518.rst:83 msgid "``ECDH-ES+A128KW``" msgstr "" #: ../../rfc/7518.rst:84 msgid "``ECDH-ES+A192KW``" msgstr "" #: ../../rfc/7518.rst:85 msgid "``ECDH-ES+A256KW``" msgstr "" #: ../../rfc/7518.rst:87 msgid "**PBES2 key derivation**:" msgstr "" #: ../../rfc/7518.rst:89 msgid "``PBES2-HS256+A128KW``" msgstr "" #: ../../rfc/7518.rst:90 msgid "``PBES2-HS384+A192KW``" msgstr "" #: ../../rfc/7518.rst:91 msgid "``PBES2-HS512+A256KW``" msgstr "" #: ../../rfc/7518.rst:93 msgid "These algorithms define how the CEK is derived, encrypted, or agreed upon." msgstr "" #: ../../rfc/7518.rst:96 msgid "JWE Content Encryption Algorithms" msgstr "" #: ../../rfc/7518.rst:98 msgid "Content encryption algorithms defined by RFC 7518 include:" msgstr "" #: ../../rfc/7518.rst:100 msgid "**AES GCM** (authenticated encryption):" msgstr "" #: ../../rfc/7518.rst:102 msgid "``A128GCM``" msgstr "" #: ../../rfc/7518.rst:103 msgid "``A192GCM``" msgstr "" #: ../../rfc/7518.rst:104 msgid "``A256GCM``" msgstr "" #: ../../rfc/7518.rst:106 msgid "**AES-CBC with HMAC SHA-2**:" msgstr "" #: ../../rfc/7518.rst:108 msgid "``A128CBC-HS256``" msgstr "" #: ../../rfc/7518.rst:109 msgid "``A192CBC-HS384``" msgstr "" #: ../../rfc/7518.rst:110 msgid "``A256CBC-HS512``" msgstr "" #: ../../rfc/7518.rst:112 msgid "" "Each algorithm specifies required key sizes, initialization vectors, " "authentication tag lengths, and validation procedures." msgstr "" #: ../../rfc/7518.rst:116 msgid "Compression Algorithms" msgstr "" #: ../../rfc/7518.rst:118 msgid "RFC 7518 defines a simple registry for compression algorithms used in JWE." msgstr "" #: ../../rfc/7518.rst:120 msgid "The only standardized algorithm is:" msgstr "" #: ../../rfc/7518.rst:122 msgid "``DEF`` — DEFLATE (RFC 1951)" msgstr "" #: ../../rfc/7518.rst:124 msgid "" "Compression is applied before encryption and controlled by the ``zip`` " "header parameter." msgstr "" #: ../../rfc/7518.rst:128 msgid "JWK Algorithm Parameters" msgstr "" #: ../../rfc/7518.rst:130 msgid "" "RFC 7518 also defines required and optional parameters for JWK keys based" " on the selected algorithm." msgstr "" #: ../../rfc/7518.rst:136 msgid "" "All algorithms and related definitions from RFC 7518 are fully " "implemented in ``joserfc``." msgstr "" #: ../../rfc/7518.rst:142 msgid "" "The underlying logic is located in the private module " "``joserfc/_rfc7518``." msgstr "" #: ../../rfc/7518.rst:144 msgid "(TODO)" msgstr "" #: ../../rfc/7519.rst:4 msgid "RFC 7519" msgstr "" #: ../../rfc/7519.rst:6 msgid "" "RFC7519 defines the JSON Web Token (JWT) specification, a compact and " "URL-safe format for representing claims securely between parties. JWTs " "are widely used for authentication, authorization, and information " "exchange in modern web applications and APIs." msgstr "" #: ../../rfc/7519.rst:11 msgid "" "A JWT can be digitally signed (JWS) or encrypted (JWE), enabling " "integrity protection, confidentiality, or both." msgstr "" #: ../../rfc/7519.rst:17 msgid "" "RFC 7519 specifies the structure, processing rules, and registered claim " "names for JSON Web Tokens. A JWT consists of three parts (for JWS) or " "five parts (for JWE), with a standardized set of claims to ensure " "interoperability across different systems." msgstr "" #: ../../rfc/7519.rst:22 msgid "Each JWT contains:" msgstr "" #: ../../rfc/7519.rst:24 msgid "**Header** — metadata describing the token type and algorithm" msgstr "" #: ../../rfc/7519.rst:25 msgid "**Payload** — a set of claims about an entity and token metadata" msgstr "" #: ../../rfc/7519.rst:26 msgid "**Signature / Authentication Tag** — used to verify integrity" msgstr "" #: ../../rfc/7519.rst:29 msgid "Registered Claim Names" msgstr "" #: ../../rfc/7519.rst:31 msgid "" "RFC 7519 defines a set of *registered* claim names that have specific, " "interoperable meanings:" msgstr "" #: ../../rfc/7519.rst:34 msgid "``iss`` — **Issuer**: identifies the principal issuing the token" msgstr "" #: ../../rfc/7519.rst:35 msgid "``sub`` — **Subject**: identifies the principal that is the subject" msgstr "" #: ../../rfc/7519.rst:36 msgid "``aud`` — **Audience**: intended recipients of the token" msgstr "" #: ../../rfc/7519.rst:37 msgid "" "``exp`` — **Expiration Time**: time after which the token must not be " "accepted" msgstr "" #: ../../rfc/7519.rst:38 msgid "``nbf`` — **Not Before**: identifies when the token becomes valid" msgstr "" #: ../../rfc/7519.rst:39 msgid "``iat`` — **Issued At**: timestamp of issuance" msgstr "" #: ../../rfc/7519.rst:40 msgid "``jti`` — **JWT ID**: unique identifier for preventing replay attacks" msgstr "" #: ../../rfc/7519.rst:42 msgid "These claims are optional unless required by the application." msgstr "" #: ../../rfc/7519.rst:45 msgid "Public and Private Claims" msgstr "" #: ../../rfc/7519.rst:47 msgid "Beyond registered claims, JWT supports:" msgstr "" #: ../../rfc/7519.rst:49 msgid "" "**Public claims** — custom claims registered in the IANA JWT Claims " "Registry" msgstr "" #: ../../rfc/7519.rst:50 msgid "" "**Private claims** — application-specific claims agreed upon by " "communicating parties" msgstr "" #: ../../rfc/7519.rst:52 msgid "" "The payload is a JSON object and can contain any key–value pairs, as long" " as they do not collide with registered claim names." msgstr "" #: ../../rfc/7519.rst:56 msgid "JWT Structure (JWS)" msgstr "" #: ../../rfc/7519.rst:58 msgid "A signed JWT uses the JWS compact serialization format:" msgstr "" #: ../../rfc/7519.rst:64 msgid "" "Each component is Base64URL-encoded. This is the most common form, used " "in OAuth 2.0, OpenID Connect, API tokens, and session systems." msgstr "" #: ../../rfc/7519.rst:68 msgid "JWT Structure (JWE)" msgstr "" #: ../../rfc/7519.rst:70 msgid "An encrypted JWT uses JWE compact serialization:" msgstr "" #: ../../rfc/7519.rst:76 msgid "" "JWE-based JWTs provide confidentiality as well as integrity, suitable for" " transmitting sensitive information." msgstr "" #: ../../rfc/7519.rst:82 msgid "All JWT features defined in RFC 7519 are implemented in ``joserfc``." msgstr "" #: ../../rfc/7520.rst:4 msgid "RFC 7520" msgstr "" #: ../../rfc/7520.rst:6 msgid "" "RFC7520 provides a comprehensive set of **examples for JOSE (JSON Object " "Signing and Encryption)**, covering JWS, JWE, and JWK. Its purpose is to " "give clear, interoperable, and implementation-friendly demonstrations of " "how JOSE specifications should be used in practice." msgstr "" #: ../../rfc/7520.rst:11 msgid "" "These examples include complete, real-world–style values for headers, " "payloads, signatures, encrypted keys, initialization vectors, ciphertext," " and authentication tags." msgstr "" #: ../../rfc/7520.rst:15 msgid "" "The examples in this RFC are frequently used as interoperability test " "vectors across JOSE implementations, including ``joserfc``." msgstr "" #: ../../rfc/7520.rst:21 msgid "RFC 7520 defines **deterministic examples** for every major JOSE workflow:" msgstr "" #: ../../rfc/7520.rst:23 msgid "Creating and validating a JWS (both compact and JSON serialization)" msgstr "" #: ../../rfc/7520.rst:24 msgid "Encrypting and decrypting a JWE (compact and JSON serialization)" msgstr "" #: ../../rfc/7520.rst:25 msgid "Multiple-signature and multiple-recipient JOSE objects" msgstr "" #: ../../rfc/7520.rst:26 msgid "Public and private JSON Web Keys (JWK)" msgstr "" #: ../../rfc/7520.rst:27 msgid "Algorithms, and key wrapping" msgstr "" #: ../../rfc/7520.rst:28 msgid "Cross-RFC examples linking RFCs 7515–7519" msgstr "" #: ../../rfc/7520.rst:30 msgid "" "These examples serve as canonical references for testing and verifying " "that implementations conform to the JOSE standards." msgstr "" #: ../../rfc/7520.rst:36 msgid "" "All examples from RFC 7520 can be validated using ``joserfc``. They are " "commonly used as test vectors in the project's test suite. You can find " "all the examples in ``tests/rfc7520``." msgstr "" #: ../../rfc/7638.rst:4 msgid "RFC 7638" msgstr "" #: ../../rfc/7638.rst:6 msgid "" "RFC7638 defines the method for computing a **JSON Web Key (JWK) " "Thumbprint**. A thumbprint is a stable, collision-resistant identifier " "derived from the key material of a JWK. It provides a secure and " "interoperable way to compare, reference, or identify keys without " "exposing the full key contents." msgstr "" #: ../../rfc/7638.rst:12 msgid "" "JWK thumbprints are commonly used in OAuth, OpenID Connect, security " "metadata documents, and JOSE-based systems that require compact and " "deterministic key identifiers." msgstr "" #: ../../rfc/7638.rst:17 msgid "Canonical JWK Form" msgstr "" #: ../../rfc/7638.rst:19 msgid "" "To ensure consistency, a JWK must be reduced to a **canonical form**. " "This includes:" msgstr "" #: ../../rfc/7638.rst:22 msgid "Only the required members for the specific key type" msgstr "" #: ../../rfc/7638.rst:23 msgid "Lexicographically sorted keys" msgstr "" #: ../../rfc/7638.rst:24 msgid "JSON without whitespace" msgstr "" #: ../../rfc/7638.rst:25 msgid "UTF-8 encoded prior to hashing" msgstr "" #: ../../rfc/7638.rst:27 msgid "Examples of required members:" msgstr "" #: ../../rfc/7638.rst:29 #, python-brace-format msgid "RSA: ``{\"e\", \"kty\", \"n\"}``" msgstr "" #: ../../rfc/7638.rst:30 #, python-brace-format msgid "EC: ``{\"crv\", \"kty\", \"x\", \"y\"}``" msgstr "" #: ../../rfc/7638.rst:31 #, python-brace-format msgid "Symmetric: ``{\"k\", \"kty\"}``" msgstr "" #: ../../rfc/7638.rst:36 ../../rfc/9278.rst:28 msgid "" "``joserfc`` implements JWK thumbprint support according to RFC7638. The " "functionality is exposed through:" msgstr "" #: ../../rfc/7638.rst:39 msgid ":meth:`joserfc.jwk.thumbprint`" msgstr "" #: ../../rfc/7638.rst:40 msgid ":meth:`joserfc.jwk.OctKey.thumbprint`" msgstr "" #: ../../rfc/7638.rst:41 msgid ":meth:`joserfc.jwk.RSAKey.thumbprint`" msgstr "" #: ../../rfc/7638.rst:42 msgid ":meth:`joserfc.jwk.ECKey.thumbprint`" msgstr "" #: ../../rfc/7638.rst:43 msgid ":meth:`joserfc.jwk.OKPKey.thumbprint`" msgstr "" #: ../../rfc/7797.rst:4 msgid "RFC 7797" msgstr "" #: ../../rfc/7797.rst:6 msgid "" "RFC7797 defines the **JSON Web Signature (JWS) Unencoded Payload " "Option**, an extension to RFC 7515 that allows the payload of a JWS to be" " transmitted **without Base64URL encoding**. This is useful for " "applications where the payload must remain in its original form, such as " "streaming data, detached content, or cases where re-encoding would be " "impractical." msgstr "" #: ../../rfc/7797.rst:13 msgid "" "This extension introduces the ``b64`` header parameter and modifies the " "rules for producing and verifying JWS signatures when payload encoding is" " disabled." msgstr "" #: ../../rfc/7797.rst:18 msgid "The ``b64`` Header Parameter" msgstr "" #: ../../rfc/7797.rst:20 msgid "" "The ``b64`` header parameter determines whether the payload of a JWS is " "Base64URL-encoded before signing:" msgstr "" #: ../../rfc/7797.rst:23 msgid "``b64 = true`` (default) — payload **must** be Base64URL-encoded" msgstr "" #: ../../rfc/7797.rst:24 msgid "``b64 = false`` — payload is included **as-is**, without encoding" msgstr "" #: ../../rfc/7797.rst:26 msgid "When ``b64`` is ``false``:" msgstr "" #: ../../rfc/7797.rst:28 msgid "It **must** appear in the *protected header*" msgstr "" #: ../../rfc/7797.rst:29 msgid "``crit`` must include ``\"b64\"``, ensuring recipients understand it" msgstr "" #: ../../rfc/7797.rst:30 msgid "The payload is transmitted in its original form (binary or text)" msgstr "" #: ../../rfc/7797.rst:31 msgid "The signing input changes accordingly" msgstr "" #: ../../rfc/7797.rst:33 msgid "" "These constraints ensure that consumers explicitly acknowledge the " "unencoded payload behavior." msgstr "" #: ../../rfc/7797.rst:39 msgid "" "``joserfc`` fully supports RFC 7797 for both signing and verifying JWS " "objects with unencoded payloads." msgstr "" #: ../../rfc/7797.rst:42 msgid "The relevant functionality is integrated default into:" msgstr "" #: ../../rfc/7797.rst:44 msgid "``jws.serialize_compact``" msgstr "" #: ../../rfc/7797.rst:45 msgid "``jws.deserialize_compact``" msgstr "" #: ../../rfc/7797.rst:46 msgid "``jws.serialize_json``" msgstr "" #: ../../rfc/7797.rst:47 msgid "``jws.deserialize_json``" msgstr "" #: ../../rfc/8037.rst:4 msgid "RFC 8037" msgstr "" #: ../../rfc/8037.rst:6 msgid "" "RFC8037 defines the use of **Edwards-Curve Digital Signature Algorithm " "(EdDSA)** for JSON Web Signature (JWS) and JSON Web Key (JWK)." msgstr "" #: ../../rfc/8037.rst:9 msgid "It introduces support for:" msgstr "" #: ../../rfc/8037.rst:11 msgid "The ``OKP`` (Octet Key Pair) JWK key type" msgstr "" #: ../../rfc/8037.rst:12 msgid "``EdDSA`` signature algorithm" msgstr "" #: ../../rfc/8037.rst:18 msgid "" "RFC 8037 extends JOSE by defining how EdDSA-based keys and signatures are" " represented and processed within the JWS and JWK frameworks." msgstr "" #: ../../rfc/8037.rst:21 msgid "The specification primarily introduces:" msgstr "" #: ../../rfc/8037.rst:23 msgid "**OKP key type** for Ed25519, Ed448, X25519, X448" msgstr "" #: ../../rfc/8037.rst:24 msgid "**JWS ``alg`` = \"EdDSA\"**" msgstr "" #: ../../rfc/8037.rst:25 msgid "Proper encoding and validation requirements using raw EdDSA signatures" msgstr "" #: ../../rfc/8037.rst:30 msgid "``joserfc`` includes full RFC 8037 support:" msgstr "" #: ../../rfc/8037.rst:32 msgid "OKP key handling (Ed25519, Ed448, X25519, X448)" msgstr "" #: ../../rfc/8037.rst:33 msgid "JWS signature creation and verification using ``EdDSA``" msgstr "" #: ../../rfc/8037.rst:34 msgid "JWK parsing and serialization for OKP keys" msgstr "" #: ../../rfc/8037.rst:39 msgid "The source code is implemented in internal modules ``joserfc/_rfc8037``." msgstr "" #: ../../rfc/8037.rst:44 msgid "" "Public classes and utilities are re-exported through ``joserfc.jwk``. You" " should always interact with ``joserfc.jwk`` module." msgstr "" #: ../../rfc/8037.rst:48 ../../rfc/8812.rst:31 ../../rfc/9864.rst:27 msgid "Example Usage" msgstr "" #: ../../rfc/8037.rst:50 msgid "Signing with an Ed25519 key:" msgstr "" #: ../../rfc/8037.rst:63 ../../rfc/8812.rst:45 ../../rfc/9864.rst:43 msgid "Verification:" msgstr "" #: ../../rfc/8037.rst:70 msgid "" "``joserfc`` handles OKP key parsing, normalization, and EdDSA signature " "validation according to RFC 8037." msgstr "" #: ../../rfc/8812.rst:4 msgid "RFC 8812" msgstr "" #: ../../rfc/8812.rst:6 msgid "" "RFC8812 defines the use of the **secp256k1** elliptic curve for JSON Web " "Signatures (JWS) and JSON Web Keys (JWK)." msgstr "" #: ../../rfc/8812.rst:9 msgid "It introduces:" msgstr "" #: ../../rfc/8812.rst:11 msgid "the new JWS algorithm identifier **ES256K**" msgstr "" #: ../../rfc/8812.rst:12 msgid "the new JWK elliptic curve identifier **secp256k1**" msgstr "" #: ../../rfc/8812.rst:14 msgid "" "This RFC extends RFC 7515 (JWS) and RFC 7517 (JWK) by adding support for " "a curve commonly used in blockchain ecosystems." msgstr "" #: ../../rfc/8812.rst:20 msgid "``joserfc`` fully supports the definitions introduced in RFC 8812:" msgstr "" #: ../../rfc/8812.rst:22 msgid "Parsing and generating EC Key with ``crv=\"secp256k1\"``" msgstr "" #: ../../rfc/8812.rst:23 msgid "Signing and verifying JWS messages using the ``ES256K`` algorithm" msgstr "" #: ../../rfc/8812.rst:25 msgid "The functionality is available through:" msgstr "" #: ../../rfc/8812.rst:27 msgid "``joserfc.jws`` for signing and verifying" msgstr "" #: ../../rfc/8812.rst:28 msgid "``joserfc.jwk`` for key loading, generation, and handling" msgstr "" #: ../../rfc/8812.rst:33 msgid "Signing with an ``secp256k1`` EC key:" msgstr "" #: ../../rfc/9278.rst:4 msgid "RFC 9278" msgstr "" #: ../../rfc/9278.rst:6 msgid "" "RFC9278 defines the **JWK Thumbprint URI**, an extension to RFC 7638 (JWK" " Thumbprint). It provides a standard way to represent a JWK Thumbprint as" " a URI, enabling stable, comparable key identifiers that can be " "referenced externally." msgstr "" #: ../../rfc/9278.rst:14 msgid "" "RFC 9278 builds on RFC 7638 by specifying how a JWK Thumbprint " "(Base64URL-encoded SHA-256 digest of a key’s canonical JSON form) can be " "expressed as a URI. This allows JWK-based key identifiers to be used in " "contexts where a URI is required." msgstr "" #: ../../rfc/9278.rst:19 msgid "The JWK Thumbprint URI uses the following format:" msgstr "" #: ../../rfc/9278.rst:31 msgid ":meth:`joserfc.jwk.thumbprint_uri`" msgstr "" #: ../../rfc/9278.rst:32 msgid ":meth:`joserfc.jwk.OctKey.thumbprint_uri`" msgstr "" #: ../../rfc/9278.rst:33 msgid ":meth:`joserfc.jwk.RSAKey.thumbprint_uri`" msgstr "" #: ../../rfc/9278.rst:34 msgid ":meth:`joserfc.jwk.ECKey.thumbprint_uri`" msgstr "" #: ../../rfc/9278.rst:35 msgid ":meth:`joserfc.jwk.OKPKey.thumbprint_uri`" msgstr "" #: ../../rfc/9278.rst:45 msgid "" "This provides a standards-compliant identifier suitable for use in any " "URI field across OAuth and JOSE-related specifications." msgstr "" #: ../../rfc/9864.rst:4 msgid "RFC 9864" msgstr "" #: ../../rfc/9864.rst:6 msgid "" "RFC9864 defines the concept of **fully-specified algorithm identifiers** " "for JOSE and COSE. Under this specification, algorithm names must " "uniquely and unambiguously determine all underlying cryptographic " "operations. As a result, ambiguous or polymorphic identifiers are " "considered deprecated." msgstr "" #: ../../rfc/9864.rst:14 msgid "**joserfc** implements ONLY the JOSE-related portions of RFC 9864." msgstr "" #: ../../rfc/9864.rst:16 msgid "" "In accordance with the specification, the use of the polymorphic " "``EdDSA`` algorithm identifier is deprecated. You should instead select a" " fully-specified algorithm:" msgstr "" #: ../../rfc/9864.rst:20 msgid "``Ed25519``" msgstr "" #: ../../rfc/9864.rst:21 msgid "``Ed448``" msgstr "" #: ../../rfc/9864.rst:23 msgid "" "By using fully-specified identifiers, you ensure deterministic, " "interoperable behavior and avoid ambiguity during algorithm negotiation." msgstr "" #: ../../rfc/9864.rst:29 msgid "Signing with an ``Ed25519`` key:" msgstr "" #: ../../rfc/index.rst:2 msgid "RFCs" msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/security.po000066400000000000000000000026351511744432500242130ustar00rootroot00000000000000# 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-12-09 14:11+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 discover a security vulnerability, **do not submit a public issue " "or patch**. Instead, please report it privately through the **GitHub " "Security** tab." msgstr "" #: ../../security.rst:8 msgid "Previous CVEs" msgstr "" #: ../../security.rst:11 msgid "CVE-2025-65015" msgstr "" #: ../../security.rst:13 msgid "Affected versions: 1.3.3, 1.3.4, 1.4.0, 1.4.1" msgstr "" #: ../../security.rst:14 msgid "Fixed versions: 1.3.5, 1.4.2" msgstr "" #: ../../security.rst:17 msgid "CVE-2024-37568" msgstr "" #: ../../security.rst:19 msgid "Fixed versions: 0.11.0" msgstr "" #: ../../security.rst:22 msgid "CWE fixes" msgstr "" #: ../../security.rst:24 msgid "CWE-290: fixed in 1.1.0" msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/sponsors.po000066400000000000000000000061021511744432500242230ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) Copyright © 2023, Hsiaoming Yang # This file is distributed under the same license as the joserfc package. # FIRST AUTHOR , 2025. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: joserfc 1.4.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-11-04 11:11+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" #: ../../sponsors.rst:5 msgid "Becoming a sponsor" msgstr "赞助我们" #: ../../sponsors.rst:9 msgid "People and organizations supporting Authlib" msgstr "Authlib 的赞助人和赞助商" #: ../../sponsors.rst:13 msgid "" "``joserfc`` is an open-source project maintained under Authlib. It is " "licensed under BSD-3-Clause and completely free to use." msgstr "" "``joserfc`` 是一个 Authlib 旗下的开源项目。它采用 BSD-3-Clause 许可证,你可以完全免费的使用它。" #: ../../sponsors.rst:17 msgid "" "To help keep the project sustainable, please consider supporting us " "through `GitHub Sponsors`_." msgstr "为帮助项目保持可持续发展,请考虑通过 `GitHub Sponsors`_ 支持我们。" #: ../../sponsors.rst msgid "Gold Sponsors" msgstr "" #: ../../sponsors.rst msgid "Polar" msgstr "" #: ../../sponsors.rst msgid "Auth0-Developers" msgstr "" #: ../../sponsors.rst msgid "Silver Sponsors" msgstr "" #: ../../sponsors.rst msgid "Codeureuses en Liberté" msgstr "" #: ../../sponsors.rst msgid "Backers" msgstr "" #: ../../sponsors.rst msgid "Roboflow" msgstr "" #: ../../sponsors.rst msgid "Yaal Coop" msgstr "" #: ../../sponsors.rst msgid "Supporter" msgstr "" #: ../../sponsors.rst msgid "Birk Jernström" msgstr "" #: ../../sponsors.rst msgid "Past Sponsors" msgstr "" #: ../../sponsors.rst msgid "Justin Ross" msgstr "" #: ../../sponsors.rst msgid "Fixer" msgstr "" #: ../../sponsors.rst msgid "Ecosystems" msgstr "" #: ../../sponsors.rst msgid "martin68" msgstr "" #: ../../sponsors.rst msgid "Microsoft" msgstr "" #: ../../sponsors.rst msgid "Redbot" msgstr "" #: ../../sponsors.rst msgid "Litestar" msgstr "" #: ../../sponsors.rst msgid "Lydia" msgstr "" #: ../../sponsors.rst msgid "Adam Hill" msgstr "" #: ../../sponsors.rst msgid "Jacob Coffee" msgstr "" #: ../../sponsors.rst msgid "Rafał Pitoń" msgstr "" #: ../../sponsors.rst msgid "Kraken Tech" msgstr "" #: ../../sponsors.rst msgid "Allen" msgstr "" #: ../../sponsors.rst msgid "Hyunwoo Park" msgstr "" #: ../../sponsors.rst msgid "Jeff Heaton" msgstr "" #: ../../sponsors.rst msgid "GitHub" msgstr "" #: ../../sponsors.rst msgid "Around" msgstr "" #: ../../sponsors.rst msgid "Malik Piara" msgstr "" #: ../../sponsors.rst msgid "Jun" msgstr "" #: ../../sponsors.rst msgid "Indeed Engineering" msgstr "" #: ../../sponsors.rst msgid "Sentry" msgstr "" #: ../../sponsors.rst msgid "Callam" msgstr "" #: ../../sponsors.rst msgid "Aveline" msgstr "" authlib-joserfc-aae7743/docs/locales/zh/LC_MESSAGES/stability.po000066400000000000000000000054461511744432500243530ustar00rootroot00000000000000# 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-06-30 14:51+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" #: ../../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 "" "``joserfc`` have released **1.0.0**, the method names and their " "parameters in modules ``joserfc.jws``, ``joserfc.jwe``, ``joserfc.jwk`` " "and ``joserfc.jwt`` 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:18 msgid "Python Versions" msgstr "" #: ../../stability.rst:20 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:25 msgid "New RFCs" msgstr "" #: ../../stability.rst:27 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:32 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:38 msgid "Upgrade notes" msgstr "" #: ../../stability.rst:40 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 "" authlib-joserfc-aae7743/docs/migrations/000077500000000000000000000000001511744432500203225ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/migrations/authlib.rst000066400000000000000000000104301511744432500225020ustar00rootroot00000000000000Migrating 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"}, "your-secret-key") .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") 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. JWK --- When using methods such as ``.as_dict``, ``.as_bytes``, ``.as_pem``, and others, ``joserfc`` uses the parameter name ``private``, whereas ``authlib.jose`` uses ``is_private``: .. code-block:: python :caption: Authlib key.as_dict(is_private=True) .. code-block:: python :caption: joserfc key.as_dict(private=True) 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, "your-secret-key") jws.deserialize_compact(value, "your-secret-key") .. code-block:: python :caption: joserfc from joserfc import jws from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") 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. authlib-joserfc-aae7743/docs/migrations/index.rst000066400000000000000000000053151511744432500221670ustar00rootroot00000000000000Migrations ========== Here are some migration guides to help you transition from other libraries to ``joserfc``: .. toctree:: authlib pyjwt python-jose Comparison ---------- joserfc is the most feature-complete Python library for the JOSE specifications, providing full coverage of all relevant RFCs. The following highlights the key differences between joserfc and other Python libraries: =============== ==================== ==================== ==================== ==================== ==================== Features joserfc authlib.jose pyjwt python-jose jwcrypto =============== ==================== ==================== ==================== ==================== ==================== Type Hints :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` Compact JWS :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` JSON JWS :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-success:`Yes` Compact JWE :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` :bdg-success:`Yes` JSON JWE :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-success:`Yes` Key generation :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`Yes` :bdg-success:`Yes` Key importing :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`Yes` :bdg-success:`Yes` JWT on JWS :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` JWT on JWE :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-success:`Yes` RFC7638 :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-success:`Yes` RFC7797 :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` RFC8037 :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` RFC8812 :bdg-success:`Yes` :bdg-success:`Yes` :bdg-success:`Yes` :bdg-danger:`No` :bdg-success:`Yes` RFC9278 :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-danger:`No` :bdg-danger:`No` RFC9864 :bdg-success:`Yes` :bdg-danger:`No` :bdg-danger:`No` :bdg-danger:`No` Not released =============== ==================== ==================== ==================== ==================== ==================== authlib-joserfc-aae7743/docs/migrations/pyjwt.rst000066400000000000000000000135731511744432500222420ustar00rootroot00000000000000Migrating 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"}, "your-secret-key", algorithm="HS256") .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") # 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, "your-secret-key", algorithms=["HS256"]) # => {"some": "payload"} .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") token = jwt.decode(encoded_jwt, key) # => token.header : {"alg": "HS256"} # => token.claims : {"some": "payload"} ``get_unverified_header`` ------------------------- PyJWT module 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]: obj = jws.extract_compact(token.encode()) return obj.protected 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`. .. _`Individual Claims Requests`: http://openid.net/specs/openid-connect-core-1_0.html#IndividualClaimsRequests PyJWKClient ----------- Unlike ``PyJWT``, ``joserfc`` does not provide a built-in HTTP client for fetching JWK Sets (jwks) from a URL. You are free to use any HTTP library you prefer to retrieve the JWK Set. Here is an example of using the **requests** library to fetch a JWK Set. .. code-block:: python import functools import requests from joserfc.jwk import KeySet from joserfc import jwt @functools.cache def fetch_jwks(url: str): resp = requests.get(jwks_uri) return KeySet.import_key_set(resp.json()) key_set = fetch_jwks('https://your-domain/to/jwks.json') token = jwt.decode(encoded_jwt, key_set) Here is a real-world example demonstrating how to fetch :ref:`azure`. authlib-joserfc-aae7743/docs/migrations/python-jose.rst000066400000000000000000000124101511744432500233310ustar00rootroot00000000000000Migrating 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'}, 'your-secret-key', algorithm='HS256') jws.verify(signed, 'your-secret-key', 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("your-secret-key") 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'}, 'your-secret-key', algorithm='HS256') jwt.decode(encoded, 'your-secret-key', algorithms='HS256') # => {'a': 'b'} .. code-block:: python :caption: joserfc from joserfc import jwt from joserfc.jwk import OctKey key = OctKey.import_key("your-secret-key") # jwt.encode(header, payload, key) encoded = jwt.encode({"alg": "HS256"}, {'a': 'b'}, key) token = jwt.decode(encoded, key) # => token.header : {"alg": "HS256"} # => token.claims : {"a": "b"} ``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]: obj = jws.extract_compact(token.encode()) return obj.protected ``get_unverified_claims`` ~~~~~~~~~~~~~~~~~~~~~~~~~ You can also use the ``jws.extract_compact`` method to extract the JWT's claims: .. code-block:: python :caption: joserfc import json from typing import Any from joserfc import jws def get_unverified_claims(token: str) -> dict[str, Any]: obj = jws.extract_compact(token.encode()) return json.loads(obj.payload) JWK --- In ``python-jose``, you can use ``jwk.construct`` to create a key instance from a JWK-formatted dictionary. In contrast, ``joserfc`` provides the ``jwk.import_key`` method to achieve the same result. .. code-block:: python :caption: joserfc from joserfc import jwk jwk.import_key({ "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" }) authlib-joserfc-aae7743/docs/recipes/000077500000000000000000000000001511744432500176005ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/recipes/azure.rst000066400000000000000000000204501511744432500214610ustar00rootroot00000000000000.. _azure: Dynamic 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 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']) return key_set 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) authlib-joserfc-aae7743/docs/recipes/openssl.rst000066400000000000000000000046201511744432500220170ustar00rootroot00000000000000Using OpenSSL command ===================== .. module:: joserfc.jwk :noindex: JOSE RFC provides a method :meth:`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 import jwk key = jwk.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 import jwk key = jwk.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 import jwk key = jwk.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 import jwk key = jwk.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 authlib-joserfc-aae7743/docs/rfc/000077500000000000000000000000001511744432500167205ustar00rootroot00000000000000authlib-joserfc-aae7743/docs/rfc/7515.rst000066400000000000000000000061161511744432500200570ustar00rootroot00000000000000.. _rfc7515: RFC 7515 ======== RFC7515 defines JSON Web Signature (JWS), a specification for representing digitally signed or MAC-protected content using JSON-based data structures. Definition ---------- RFC 7515 specifies the complete framework for creating and validating JSON Web Signatures. The specification includes: JWS Header ~~~~~~~~~~ The JWS Header is a set of metadata that describes how the JWS object is constructed and verified. RFC 7515 defines a registry of header parameters, including: - ``alg`` — the algorithm used to generate the signature or MAC (required) - ``jwk`` / ``jku`` — a JSON Web Key or URL pointing to a key set - ``kid`` — a key identifier that helps recipients select the correct key - ``typ`` and ``cty`` — type and content-type hints - ``crit`` — a list of critical header parameters that must be understood by the verifier The header **must** be encoded as a Base64URL-encoded JSON object when used in compact serialization, and may appear as either a protected or unprotected header in JSON serialization. Compact Serialization ~~~~~~~~~~~~~~~~~~~~~ The compact serialization format represents a JWS as a single, period-separated string consisting of three parts: .. code-block:: text .. Each part is Base64URL-encoded. This format is designed to be: - minimal and URL-safe - suitable for HTTP headers, query parameters, and tokens such as JWT Compact serialization supports exactly **one signature**, with no unprotected headers. JSON Serialization ~~~~~~~~~~~~~~~~~~ JSON serialization uses a structured JSON object to represent a JWS. It is designed for use cases that require more flexibility than the compact form. A JSON-serialized JWS can include: - **multiple signatures** over the same payload - both **protected** and **unprotected** headers - richer metadata, such as per-signature header values The primary fields defined in RFC 7515 are: - ``payload`` — the Base64URL-encoded content - ``signatures`` — an array of signature objects, each containing: - ``protected`` — a Base64URL-encoded header - ``header`` — an unprotected header (optional) - ``signature`` — the Base64URL-encoded signature value JSON serialization is not intended for compact transmission but is ideal for multi-party signing, debugging, and more expressive JOSE workflows. Implementation -------------- All features defined in RFC 7515 are fully implemented in ``joserfc``. Private modules ~~~~~~~~~~~~~~~ The source code resides in the private module ``joserfc/_rfc7515``: - JWS algorithms base models - JWS algorithm registry - Compact Serialization - JSON Serialization Public exports ~~~~~~~~~~~~~~ You should always interact with JWS functionality via ``joserfc.jws``, rather than importing from the private module directly. - :meth:`joserfc.jws.serialize_compact`: Construct JWS compact serialization. - :meth:`joserfc.jws.deserialize_compact`: Parse JWS compact serialization. - :meth:`joserfc.jws.serialize_json`: Construct JWS JSON serialization. - :meth:`joserfc.jws.deserialize_json`: Parse JWS JSON serialization. authlib-joserfc-aae7743/docs/rfc/7516.rst000066400000000000000000000077751511744432500200740ustar00rootroot00000000000000.. _rfc7516: RFC 7516 ======== RFC7516 defines JSON Web Encryption (JWE), a specification for representing encrypted content using JSON-based data structures. It describes how to encrypt arbitrary payloads, including how keys are managed, how encryption parameters are represented, and how JWE objects are serialized. Definition ---------- RFC 7516 specifies the complete framework for creating and decrypting JSON Web Encryption (JWE) objects. The specification defines: JWE Header ~~~~~~~~~~ The JWE Header is a JSON object that specifies metadata describing how the payload is encrypted and how the Content Encryption Key (CEK) is managed. RFC 7516 defines a registry of header parameters, including: - ``alg`` — the key management algorithm used to encrypt or wrap the CEK (required) - ``enc`` — the content encryption algorithm used to protect the payload (required) - ``jwk`` / ``jku`` — a JSON Web Key or URL pointing to a key set - ``kid`` — a key identifier to help recipients locate the correct key - ``zip`` — indicates that the plaintext is compressed before encryption - ``apu`` / ``apv`` — agreement PartyU/PartyV info for ECDH-based algorithms - ``typ`` and ``cty`` — type and content-type hints - ``crit`` — critical header parameters that must be understood In the compact serialization format, the header is Base64URL-encoded as the first segment of the JWE. In JSON serialization, it may appear as either a protected or unprotected header, similar to JWS. Compact Serialization ~~~~~~~~~~~~~~~~~~~~~ The compact serialization format represents a JWE as a single, period-separated string consisting of **five** Base64URL-encoded parts: .. code-block:: text .... Each part has a specific meaning: * ``protected-header`` — metadata describing algorithms and parameters * ``encrypted-key`` — the CEK encrypted or wrapped with the recipient's key * ``iv`` — initialization vector for the content encryption algorithm * ``ciphertext`` — the encrypted payload * ``tag`` — the authentication tag for AEAD algorithms Compact serialization is designed for simple and compact transmission and supports exactly **one recipient**. JSON Serialization ~~~~~~~~~~~~~~~~~~ The JSON serialization format expresses a JWE as a JSON object and supports more complex use cases than compact serialization. It is used when: - encrypting for **multiple recipients** - including per-recipient header parameters - debugging or inspecting encryption metadata A JSON-serialized JWE typically includes the following fields: - ``protected`` — a Base64URL-encoded header shared across recipients - ``unprotected`` — an unprotected shared header (optional) - ``iv`` — the initialization vector - ``ciphertext`` — the encrypted payload - ``tag`` — the authentication tag - ``recipients`` — an array of recipient objects, each containing: - ``header`` — per-recipient unprotected header (optional) - ``encrypted_key`` — a CEK encrypted for that recipient JSON serialization enables multi-recipient encrypted messages and greater flexibility than the compact format. Implementation -------------- All features defined in RFC 7516 are fully implemented in ``joserfc``. Private modules ~~~~~~~~~~~~~~~ The underlying implementation can be found in the private module ``joserfc/_rfc7516``: - JWE algorithms base models - Compact Serialization: encryption and decryption - JSON Serialization: encryption and decryption Public exports ~~~~~~~~~~~~~~ These internals are re-exported through the public API in ``joserfc.jwe``. You should always use ``joserfc.jwe`` for creating, encrypting, decrypting, and validating JWEs, and avoid importing from the private module directly. - :meth:`joserfc.jwe.encrypt_compact`: Encrypt JWE with compact serialization. - :meth:`joserfc.jwe.decrypt_compact`: Decrypt JWE with compact serialization. - :meth:`joserfc.jwe.encrypt_json`: Encrypt JWE with JSON serialization. - :meth:`joserfc.jwe.decrypt_json`: Decrypt JWE with JSON serialization. authlib-joserfc-aae7743/docs/rfc/7517.rst000066400000000000000000000050621511744432500200600ustar00rootroot00000000000000.. _rfc7517: RFC 7517 ======== RFC7517 defines the JSON Web Key (JWK) specification, a standard format for representing cryptographic keys using JSON. It provides a flexible and interoperable way to describe public keys, private keys, and symmetric keys for use with JWS, JWE, and other JOSE-related operations. Definition ---------- RFC 7517 specifies how cryptographic keys are expressed as JSON objects and how sets of keys are represented. The specification defines: JWK Object ~~~~~~~~~~ A JWK is a JSON object representing a single cryptographic key. Each JWK contains a set of required and optional parameters depending on the key type. Common parameters include: - ``kty`` — the key type (required), such as ``RSA``, ``EC``, or ``oct`` - ``use`` — intended key usage (``sig`` for signature, ``enc`` for encryption) - ``key_ops`` — a list of permitted operations (``sign``, ``verify``, ``wrapKey``, etc.) - ``kid`` — a key identifier for selecting a specific key - ``alg`` — the algorithm for which the key is intended - ``x5u`` / ``x5c`` / ``x5t`` — X.509 certificate chain parameters JWK Set (JWKS) ~~~~~~~~~~~~~~ A JWK Set is a JSON object that contains an array of JWKs. It is commonly used for publishing multiple keys, such as rotation sets or multi-tenant public keys: .. code-block:: text { "keys": [ { ... JWK 1 ... }, { ... JWK 2 ... } ] } JWKS documents are frequently served over HTTPS endpoints, allowing clients to discover signing keys dynamically (e.g., OAuth 2.0, OpenID Connect). Key Usage and Operations ~~~~~~~~~~~~~~~~~~~~~~~~ A JWK may declare either: - ``use`` — a coarse-grained indication of intended purpose (e.g., ``sig`` or ``enc``), or - ``key_ops`` — a precise list of permissible operations (e.g., ``sign``, ``verify``, ``encrypt``, ``unwrapKey``) These fields help recipients determine how a key should be used and prevent unintended or insecure key usage. Implementation -------------- All definitions from RFC 7517 are fully implemented in ``joserfc``. Private modules ~~~~~~~~~~~~~~~ The underlying logic resides in the private module ``joserfc/_rfc7517``, which defines: - Base key models - Utilities for handling PEM keys Public exports ~~~~~~~~~~~~~~ Public classes and utilities are re-exported through ``joserfc.jwk``. You should always use ``joserfc.jwk`` for working with JWK objects and JWK Sets, rather than importing from the private module directly. This includes functionality for: - parsing JWK and JWKs: ``jwk.import_key`` - constructing JWK and JWKs: ``jwk.generate_key`` authlib-joserfc-aae7743/docs/rfc/7518.rst000066400000000000000000000062531511744432500200640ustar00rootroot00000000000000.. _rfc7518: RFC 7518 ======== RFC7518 defines the JSON Web Algorithms (JWA) specification, which lists the cryptographic algorithms and associated parameters used with JWS (JSON Web Signature), JWE (JSON Web Encryption), and JWK (JSON Web Key). It standardizes algorithm names, required key properties, and security considerations to ensure interoperable and secure JOSE implementations. Definition ---------- RFC 7518 provides a complete registry of algorithms used across the JOSE ecosystem. It defines algorithms for: - digital signatures and MACs (for JWS) - key management and key wrapping (for JWE) - content encryption (for JWE) - compression methods - associated key parameters for JWK The specification ensures that JOSE objects can be securely encoded, signed, encrypted, and decrypted using consistent algorithm names and structures. JWS Algorithms ~~~~~~~~~~~~~~ The signature and MAC algorithms defined for JWS include: - **Using the Algorithm "none"** (not secure) - **HMAC with SHA-2 functions**: - ``HS256`` - ``HS384`` - ``HS512`` - **RSA PKCS#1 v1.5 signatures**: - ``RS256`` - ``RS384`` - ``RS512`` - **RSA-PSS signatures**: - ``PS256`` - ``PS384`` - ``PS512`` - **Elliptic Curve signatures**: - ``ES256`` (P-256 + SHA-256) - ``ES384`` (P-384 + SHA-384) - ``ES512`` (P-521 + SHA-512) The specification defines requirements such as key sizes, curve types, and verification rules. JWE Key Management Algorithms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ JWE uses key management algorithms to establish or wrap the Content Encryption Key (CEK). RFC 7518 defines: - **RSA-based key management**: - ``RSA1_5`` (deprecated for new deployments) - ``RSA-OAEP`` - ``RSA-OAEP-256`` - **AES Key Wrap**: - ``A128KW`` - ``A192KW`` - ``A256KW`` - **Elliptic Curve Diffie–Hellman**: - ``ECDH-ES`` (direct) - ``ECDH-ES+A128KW`` - ``ECDH-ES+A192KW`` - ``ECDH-ES+A256KW`` - **PBES2 key derivation**: - ``PBES2-HS256+A128KW`` - ``PBES2-HS384+A192KW`` - ``PBES2-HS512+A256KW`` These algorithms define how the CEK is derived, encrypted, or agreed upon. JWE Content Encryption Algorithms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Content encryption algorithms defined by RFC 7518 include: - **AES GCM** (authenticated encryption): - ``A128GCM`` - ``A192GCM`` - ``A256GCM`` - **AES-CBC with HMAC SHA-2**: - ``A128CBC-HS256`` - ``A192CBC-HS384`` - ``A256CBC-HS512`` Each algorithm specifies required key sizes, initialization vectors, authentication tag lengths, and validation procedures. Compression Algorithms ~~~~~~~~~~~~~~~~~~~~~~ RFC 7518 defines a simple registry for compression algorithms used in JWE. The only standardized algorithm is: - ``DEF`` — DEFLATE (RFC 1951) Compression is applied before encryption and controlled by the ``zip`` header parameter. JWK Algorithm Parameters ~~~~~~~~~~~~~~~~~~~~~~~~ RFC 7518 also defines required and optional parameters for JWK keys based on the selected algorithm. Implementation -------------- All algorithms and related definitions from RFC 7518 are fully implemented in ``joserfc``. Private modules ~~~~~~~~~~~~~~~ The underlying logic is located in the private module ``joserfc/_rfc7518``. (TODO) authlib-joserfc-aae7743/docs/rfc/7519.rst000066400000000000000000000052341511744432500200630ustar00rootroot00000000000000.. _rfc7519: RFC 7519 ======== RFC7519 defines the JSON Web Token (JWT) specification, a compact and URL-safe format for representing claims securely between parties. JWTs are widely used for authentication, authorization, and information exchange in modern web applications and APIs. A JWT can be digitally signed (JWS) or encrypted (JWE), enabling integrity protection, confidentiality, or both. Definition ---------- RFC 7519 specifies the structure, processing rules, and registered claim names for JSON Web Tokens. A JWT consists of three parts (for JWS) or five parts (for JWE), with a standardized set of claims to ensure interoperability across different systems. Each JWT contains: - **Header** — metadata describing the token type and algorithm - **Payload** — a set of claims about an entity and token metadata - **Signature / Authentication Tag** — used to verify integrity Registered Claim Names ~~~~~~~~~~~~~~~~~~~~~~ RFC 7519 defines a set of *registered* claim names that have specific, interoperable meanings: - ``iss`` — **Issuer**: identifies the principal issuing the token - ``sub`` — **Subject**: identifies the principal that is the subject - ``aud`` — **Audience**: intended recipients of the token - ``exp`` — **Expiration Time**: time after which the token must not be accepted - ``nbf`` — **Not Before**: identifies when the token becomes valid - ``iat`` — **Issued At**: timestamp of issuance - ``jti`` — **JWT ID**: unique identifier for preventing replay attacks These claims are optional unless required by the application. Public and Private Claims ~~~~~~~~~~~~~~~~~~~~~~~~~ Beyond registered claims, JWT supports: - **Public claims** — custom claims registered in the IANA JWT Claims Registry - **Private claims** — application-specific claims agreed upon by communicating parties The payload is a JSON object and can contain any key–value pairs, as long as they do not collide with registered claim names. JWT Structure (JWS) ~~~~~~~~~~~~~~~~~~~ A signed JWT uses the JWS compact serialization format: .. code-block:: text
.. Each component is Base64URL-encoded. This is the most common form, used in OAuth 2.0, OpenID Connect, API tokens, and session systems. JWT Structure (JWE) ~~~~~~~~~~~~~~~~~~~ An encrypted JWT uses JWE compact serialization: .. code-block:: text
.... JWE-based JWTs provide confidentiality as well as integrity, suitable for transmitting sensitive information. Implementation -------------- All JWT features defined in RFC 7519 are implemented in ``joserfc``. Private modules ~~~~~~~~~~~~~~~ Public exports ~~~~~~~~~~~~~~ authlib-joserfc-aae7743/docs/rfc/7520.rst000066400000000000000000000025161511744432500200530ustar00rootroot00000000000000.. _rfc7520: RFC 7520 ======== RFC7520 provides a comprehensive set of **examples for JOSE (JSON Object Signing and Encryption)**, covering JWS, JWE, and JWK. Its purpose is to give clear, interoperable, and implementation-friendly demonstrations of how JOSE specifications should be used in practice. These examples include complete, real-world–style values for headers, payloads, signatures, encrypted keys, initialization vectors, ciphertext, and authentication tags. The examples in this RFC are frequently used as interoperability test vectors across JOSE implementations, including ``joserfc``. Definition ---------- RFC 7520 defines **deterministic examples** for every major JOSE workflow: - Creating and validating a JWS (both compact and JSON serialization) - Encrypting and decrypting a JWE (compact and JSON serialization) - Multiple-signature and multiple-recipient JOSE objects - Public and private JSON Web Keys (JWK) - Algorithms, and key wrapping - Cross-RFC examples linking RFCs 7515–7519 These examples serve as canonical references for testing and verifying that implementations conform to the JOSE standards. Implementation -------------- All examples from RFC 7520 can be validated using ``joserfc``. They are commonly used as test vectors in the project's test suite. You can find all the examples in ``tests/rfc7520``. authlib-joserfc-aae7743/docs/rfc/7638.rst000066400000000000000000000023141511744432500200610ustar00rootroot00000000000000.. _rfc7638: RFC 7638 ======== RFC7638 defines the method for computing a **JSON Web Key (JWK) Thumbprint**. A thumbprint is a stable, collision-resistant identifier derived from the key material of a JWK. It provides a secure and interoperable way to compare, reference, or identify keys without exposing the full key contents. JWK thumbprints are commonly used in OAuth, OpenID Connect, security metadata documents, and JOSE-based systems that require compact and deterministic key identifiers. Canonical JWK Form ------------------ To ensure consistency, a JWK must be reduced to a **canonical form**. This includes: - Only the required members for the specific key type - Lexicographically sorted keys - JSON without whitespace - UTF-8 encoded prior to hashing Examples of required members: - RSA: ``{"e", "kty", "n"}`` - EC: ``{"crv", "kty", "x", "y"}`` - Symmetric: ``{"k", "kty"}`` Implementation -------------- ``joserfc`` implements JWK thumbprint support according to RFC7638. The functionality is exposed through: - :meth:`joserfc.jwk.thumbprint` - :meth:`joserfc.jwk.OctKey.thumbprint` - :meth:`joserfc.jwk.RSAKey.thumbprint` - :meth:`joserfc.jwk.ECKey.thumbprint` - :meth:`joserfc.jwk.OKPKey.thumbprint` authlib-joserfc-aae7743/docs/rfc/7797.rst000066400000000000000000000027411511744432500200730ustar00rootroot00000000000000.. _rfc7797: RFC 7797 ======== RFC7797 defines the **JSON Web Signature (JWS) Unencoded Payload Option**, an extension to RFC 7515 that allows the payload of a JWS to be transmitted **without Base64URL encoding**. This is useful for applications where the payload must remain in its original form, such as streaming data, detached content, or cases where re-encoding would be impractical. This extension introduces the ``b64`` header parameter and modifies the rules for producing and verifying JWS signatures when payload encoding is disabled. The ``b64`` Header Parameter ---------------------------- The ``b64`` header parameter determines whether the payload of a JWS is Base64URL-encoded before signing: - ``b64 = true`` (default) — payload **must** be Base64URL-encoded - ``b64 = false`` — payload is included **as-is**, without encoding When ``b64`` is ``false``: - It **must** appear in the *protected header* - ``crit`` must include ``"b64"``, ensuring recipients understand it - The payload is transmitted in its original form (binary or text) - The signing input changes accordingly These constraints ensure that consumers explicitly acknowledge the unencoded payload behavior. Implementation -------------- ``joserfc`` fully supports RFC 7797 for both signing and verifying JWS objects with unencoded payloads. The relevant functionality is integrated default into: - ``jws.serialize_compact`` - ``jws.deserialize_compact`` - ``jws.serialize_json`` - ``jws.deserialize_json`` authlib-joserfc-aae7743/docs/rfc/8037.rst000066400000000000000000000033321511744432500200540ustar00rootroot00000000000000.. _rfc8037: RFC 8037 ======== RFC8037 defines the use of **Edwards-Curve Digital Signature Algorithm (EdDSA)** for JSON Web Signature (JWS) and JSON Web Key (JWK). It introduces support for: - The ``OKP`` (Octet Key Pair) JWK key type - ``EdDSA`` signature algorithm Definition ---------- RFC 8037 extends JOSE by defining how EdDSA-based keys and signatures are represented and processed within the JWS and JWK frameworks. The specification primarily introduces: - **OKP key type** for Ed25519, Ed448, X25519, X448 - **JWS ``alg`` = "EdDSA"** - Proper encoding and validation requirements using raw EdDSA signatures Implementation -------------- ``joserfc`` includes full RFC 8037 support: - OKP key handling (Ed25519, Ed448, X25519, X448) - JWS signature creation and verification using ``EdDSA`` - JWK parsing and serialization for OKP keys Private modules ~~~~~~~~~~~~~~~ The source code is implemented in internal modules ``joserfc/_rfc8037``. Public exports ~~~~~~~~~~~~~~ Public classes and utilities are re-exported through ``joserfc.jwk``. You should always interact with ``joserfc.jwk`` module. Example Usage ------------- Signing with an Ed25519 key: .. code-block:: python from joserfc import jws from joserfc.jwk import OKPKey private_key = OKPKey.generate_key("Ed25519") payload = b"hello" protected = {"alg": "EdDSA"} output = jws.serialize_compact(protected, payload, private_key, algorithms=["EdDSA"]) Verification: .. code-block:: python public_key = OKPKey.import_key(private_key.as_dict(private=False)) jws.deserialize_compact(output, public_key, algorithms=["EdDSA"]) ``joserfc`` handles OKP key parsing, normalization, and EdDSA signature validation according to RFC 8037. authlib-joserfc-aae7743/docs/rfc/8812.rst000066400000000000000000000023631511744432500200600ustar00rootroot00000000000000.. _rfc8812: RFC 8812 ======== RFC8812 defines the use of the **secp256k1** elliptic curve for JSON Web Signatures (JWS) and JSON Web Keys (JWK). It introduces: - the new JWS algorithm identifier **ES256K** - the new JWK elliptic curve identifier **secp256k1** This RFC extends RFC 7515 (JWS) and RFC 7517 (JWK) by adding support for a curve commonly used in blockchain ecosystems. Implementation -------------- ``joserfc`` fully supports the definitions introduced in RFC 8812: - Parsing and generating EC Key with ``crv="secp256k1"`` - Signing and verifying JWS messages using the ``ES256K`` algorithm The functionality is available through: - ``joserfc.jws`` for signing and verifying - ``joserfc.jwk`` for key loading, generation, and handling Example Usage ------------- Signing with an ``secp256k1`` EC key: .. code-block:: python from joserfc import jws, jwk private_key = jwk.generate_key("EC", "secp256k1") payload = b"hello" protected = {"alg": "ES256K"} output = jws.serialize_compact(protected, payload, private_key, algorithms=["ES256K"]) Verification: .. code-block:: python public_key = jwk.import_key(private_key.as_dict(private=False)) jws.deserialize_compact(output, public_key, algorithms=["ES256K"]) authlib-joserfc-aae7743/docs/rfc/9278.rst000066400000000000000000000025311511744432500200640ustar00rootroot00000000000000.. _rfc9278: RFC 9278 ======== RFC9278 defines the **JWK Thumbprint URI**, an extension to RFC 7638 (JWK Thumbprint). It provides a standard way to represent a JWK Thumbprint as a URI, enabling stable, comparable key identifiers that can be referenced externally. Definition ---------- RFC 9278 builds on RFC 7638 by specifying how a JWK Thumbprint (Base64URL-encoded SHA-256 digest of a key’s canonical JSON form) can be expressed as a URI. This allows JWK-based key identifiers to be used in contexts where a URI is required. The JWK Thumbprint URI uses the following format: .. code-block:: text urn:ietf:params:oauth:jwk-thumbprint:sha-256: Implementation -------------- ``joserfc`` implements JWK thumbprint support according to RFC7638. The functionality is exposed through: - :meth:`joserfc.jwk.thumbprint_uri` - :meth:`joserfc.jwk.OctKey.thumbprint_uri` - :meth:`joserfc.jwk.RSAKey.thumbprint_uri` - :meth:`joserfc.jwk.ECKey.thumbprint_uri` - :meth:`joserfc.jwk.OKPKey.thumbprint_uri` .. code-block:: python from joserfc import jwk key = jwk.generate_key("RSA") key.thumbprint_uri() # 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:OLO1-f_Vl8mishRR_ZuXJFqZn1geoCXGsJdmKlW13Oc' This provides a standards-compliant identifier suitable for use in any URI field across OAuth and JOSE-related specifications. authlib-joserfc-aae7743/docs/rfc/9864.rst000066400000000000000000000024621511744432500200700ustar00rootroot00000000000000.. _rfc9864: RFC 9864 ======== RFC9864 defines the concept of **fully-specified algorithm identifiers** for JOSE and COSE. Under this specification, algorithm names must uniquely and unambiguously determine all underlying cryptographic operations. As a result, ambiguous or polymorphic identifiers are considered deprecated. Implementation -------------- **joserfc** implements ONLY the JOSE-related portions of RFC 9864. In accordance with the specification, the use of the polymorphic ``EdDSA`` algorithm identifier is deprecated. You should instead select a fully-specified algorithm: - ``Ed25519`` - ``Ed448`` By using fully-specified identifiers, you ensure deterministic, interoperable behavior and avoid ambiguity during algorithm negotiation. Example Usage ------------- Signing with an ``Ed25519`` key: .. code-block:: python from joserfc import jws from joserfc.jwk import OKPKey private_key = OKPKey.generate_key("Ed25519") payload = b"hello" # using Ed25519 instead of EdDSA protected = {"alg": "Ed25519"} output = jws.serialize_compact(protected, payload, private_key, algorithms=["Ed25519"]) Verification: .. code-block:: python public_key = OKPKey.import_key(private_key.as_dict(private=False)) jws.deserialize_compact(output, public_key, algorithms=["Ed25519"]) authlib-joserfc-aae7743/docs/rfc/index.rst000066400000000000000000000002051511744432500205560ustar00rootroot00000000000000RFCs ==== .. toctree:: 7515 7516 7517 7518 7519 7520 7638 7797 8037 8812 9278 9864 authlib-joserfc-aae7743/docs/security.rst000066400000000000000000000006451511744432500205540ustar00rootroot00000000000000Security ======== If you discover a security vulnerability, **do not submit a public issue or patch**. Instead, please report it privately through the **GitHub Security** tab. Previous CVEs ------------- CVE-2025-65015 ~~~~~~~~~~~~~~ - Affected versions: 1.3.3, 1.3.4, 1.4.0, 1.4.1 - Fixed versions: 1.3.5, 1.4.2 CVE-2024-37568 ~~~~~~~~~~~~~~ Fixed versions: 0.11.0 CWE fixes ---------- - CWE-290: fixed in 1.1.0 authlib-joserfc-aae7743/docs/sponsors.rst000066400000000000000000000014411511744432500205660ustar00rootroot00000000000000:orphan: :layout: simple Becoming a sponsor ================== .. rst-class:: lead People and organizations supporting Authlib ------ ``joserfc`` is an open-source project maintained under Authlib. It is licensed under BSD-3-Clause and completely free to use. To help keep the project sustainable, please consider supporting us through `GitHub Sponsors`_. .. _`GitHub Sponsors`: https://github.com/sponsors/authlib .. sponsors:: Gold Sponsors :amount: 100 :size: 2xl :show-name: .. sponsors:: Silver Sponsors :amount: 50, 100 :size: xl .. sponsors:: Sponsors :amount: 25, 50 :size: md .. sponsors:: Backers :amount: 10, 25 :size: sm .. sponsors:: Supporter :amount: 1, 10 :size: sm .. sponsors:: Past Sponsors :amount: 0 :size: xs authlib-joserfc-aae7743/docs/stability.rst000066400000000000000000000032621511744432500207070ustar00rootroot00000000000000API 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 ---------- ``joserfc`` have released **1.0.0**, the method names and their parameters in modules ``joserfc.jws``, ``joserfc.jwe``, ``joserfc.jwk`` and ``joserfc.jwt`` 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. authlib-joserfc-aae7743/public/000077500000000000000000000000001511744432500164745ustar00rootroot00000000000000authlib-joserfc-aae7743/public/.nojekyll000066400000000000000000000000001511744432500203120ustar00rootroot00000000000000authlib-joserfc-aae7743/public/CNAME000066400000000000000000000000211511744432500172330ustar00rootroot00000000000000jose.authlib.org authlib-joserfc-aae7743/public/index.html000066400000000000000000000003471511744432500204750ustar00rootroot00000000000000 Redirecting to https://jose.authlib.org/en/ authlib-joserfc-aae7743/public/robots.txt000066400000000000000000000000751511744432500205470ustar00rootroot00000000000000User-agent: * Sitemap: https://jose.authlib.org/sitemap.xml authlib-joserfc-aae7743/pyproject.toml000066400000000000000000000064711511744432500201420ustar00rootroot00000000000000[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>=45.0.1", ] license = {text = "BSD-3-Clause"} requires-python = ">=3.9" 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.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Security", "Topic :: Security :: Cryptography", ] [project.optional-dependencies] drafts = ["pycryptodome"] [project.urls] Documentation = "https://jose.authlib.org/" Source = "https://github.com/authlib/joserfc" Funding = "https://github.com/sponsors/authlib" 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 = [ "pycryptodome", # for testing drafts "mypy", "pytest", "pytest-cov", "ruff", "pre-commit", ] docs = [ "shibuya", "sphinx", "sphinx-contributors", "sphinx-copybutton", "sphinx-design", "sphinx-intl", "sphinx-sitemap", "sphinx-iconify", ] [tool.ruff] line-length = 120 [tool.pytest.ini_options] pythonpath = ["src", "."] testpaths = ["tests"] filterwarnings = [ "error::DeprecationWarning", # ignore UserWarning instead of SecurityWarning to # prevent coverage not working # "ignore::joserfc.errors.SecurityWarning" "ignore::UserWarning", ] [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.9" files = ["src/joserfc"] show_error_codes = true pretty = true [tool.tox] requires = ["tox>=4.19"] env_list = [ "style", "py310", "py311", "py312", "py313", "py314", "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}"], ] authlib-joserfc-aae7743/requirements-dev.lock000066400000000000000000000025171511744432500213740ustar00rootroot00000000000000# This file was autogenerated by uv via the following command: # uv export --no-hashes -o requirements-dev.lock -e . cffi==2.0.0 ; platform_python_implementation != 'PyPy' cfgv==3.4.0 ; python_full_version < '3.10' cfgv==3.5.0 ; python_full_version >= '3.10' colorama==0.4.6 ; sys_platform == 'win32' coverage==7.10.7 ; python_full_version < '3.10' coverage==7.12.0 ; python_full_version >= '3.10' cryptography==46.0.3 distlib==0.4.0 exceptiongroup==1.3.1 ; python_full_version < '3.11' filelock==3.19.1 ; python_full_version < '3.10' filelock==3.20.0 ; python_full_version >= '3.10' identify==2.6.15 iniconfig==2.1.0 ; python_full_version < '3.10' iniconfig==2.3.0 ; python_full_version >= '3.10' mypy==1.18.2 mypy-extensions==1.1.0 nodeenv==1.9.1 packaging==25.0 pathspec==0.12.1 platformdirs==4.4.0 ; python_full_version < '3.10' platformdirs==4.5.0 ; python_full_version >= '3.10' pluggy==1.6.0 pre-commit==4.3.0 ; python_full_version < '3.10' pre-commit==4.5.0 ; python_full_version >= '3.10' pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' pycryptodome==3.23.0 pygments==2.19.2 pytest==8.4.2 ; python_full_version < '3.10' pytest==9.0.1 ; python_full_version >= '3.10' pytest-cov==7.0.0 pyyaml==6.0.3 ruff==0.14.6 tomli==2.3.0 ; python_full_version <= '3.11' typing-extensions==4.15.0 virtualenv==20.35.4 authlib-joserfc-aae7743/requirements-docs.lock000066400000000000000000000047401511744432500215460ustar00rootroot00000000000000# This file was autogenerated by uv via the following command: # uv export --no-hashes --group docs -o requirements-docs.lock -e . alabaster==0.7.16 ; python_full_version < '3.10' alabaster==1.0.0 ; python_full_version >= '3.10' babel==2.17.0 certifi==2025.11.12 cffi==2.0.0 ; platform_python_implementation != 'PyPy' cfgv==3.4.0 ; python_full_version < '3.10' cfgv==3.5.0 ; python_full_version >= '3.10' charset-normalizer==3.4.4 click==8.1.8 ; python_full_version < '3.10' click==8.3.1 ; python_full_version >= '3.10' colorama==0.4.6 ; sys_platform == 'win32' coverage==7.10.7 ; python_full_version < '3.10' coverage==7.12.0 ; python_full_version >= '3.10' cryptography==46.0.3 distlib==0.4.0 docutils==0.21.2 exceptiongroup==1.3.1 ; python_full_version < '3.11' filelock==3.19.1 ; python_full_version < '3.10' filelock==3.20.0 ; python_full_version >= '3.10' identify==2.6.15 idna==3.11 imagesize==1.4.1 importlib-metadata==8.7.0 ; python_full_version < '3.10' iniconfig==2.1.0 ; python_full_version < '3.10' iniconfig==2.3.0 ; python_full_version >= '3.10' jinja2==3.1.6 markupsafe==3.0.3 mypy==1.18.2 mypy-extensions==1.1.0 nodeenv==1.9.1 packaging==25.0 pathspec==0.12.1 platformdirs==4.4.0 ; python_full_version < '3.10' platformdirs==4.5.0 ; python_full_version >= '3.10' pluggy==1.6.0 pre-commit==4.3.0 ; python_full_version < '3.10' pre-commit==4.5.0 ; python_full_version >= '3.10' pycparser==2.23 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' pycryptodome==3.23.0 pygments==2.19.2 pygments-styles==0.3.0 pytest==8.4.2 ; python_full_version < '3.10' pytest==9.0.1 ; python_full_version >= '3.10' pytest-cov==7.0.0 pyyaml==6.0.3 requests==2.32.5 roman-numerals-py==3.1.0 ; python_full_version >= '3.11' ruff==0.14.6 shibuya==2025.10.21 ; python_full_version < '3.10' shibuya==2025.11.10 ; python_full_version >= '3.10' snowballstemmer==3.0.1 sphinx==7.4.7 ; python_full_version < '3.10' 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.6.1 sphinx-iconify==0.2.1 sphinx-intl==2.3.2 sphinx-last-updated-by-git==0.3.8 sphinx-sitemap==2.9.0 sphinxcontrib-applehelp==2.0.0 sphinxcontrib-devhelp==2.0.0 sphinxcontrib-htmlhelp==2.1.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==2.0.0 sphinxcontrib-serializinghtml==2.0.0 tomli==2.3.0 ; python_full_version <= '3.11' typing-extensions==4.15.0 urllib3==2.5.0 virtualenv==20.35.4 zipp==3.23.0 ; python_full_version < '3.10' authlib-joserfc-aae7743/serve.py000066400000000000000000000005221511744432500167130ustar00rootroot00000000000000from 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") authlib-joserfc-aae7743/sonar-project.properties000066400000000000000000000003641511744432500221250ustar00rootroot00000000000000sonar.projectKey=authlib_joserfc sonar.organization=authlib sonar.sources=src sonar.sourceEncoding=UTF-8 sonar.test.inclusions=tests/**/test_*.py sonar.python.version=3.9, 3.10, 3.11, 3.12, 3.13 sonar.python.coverage.reportPaths=coverage.xml authlib-joserfc-aae7743/src/000077500000000000000000000000001511744432500160055ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/000077500000000000000000000000001511744432500174405ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/__init__.py000066400000000000000000000002201511744432500215430ustar00rootroot00000000000000__version__ = "1.6.0" __homepage__ = "https://jose.authlib.org/en/" __author__ = "Hsiaoming Yang " __license__ = "BSD-3-Clause" authlib-joserfc-aae7743/src/joserfc/_keys.py000066400000000000000000000150011511744432500211210ustar00rootroot00000000000000from __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: 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": list[DictKey]}) class KeySet: #: keys in the key set keys: list[Key] registry_cls: t.Type[JWKRegistry] = JWKRegistry algorithm_keys: t.ClassVar[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, parameters: KeyParameters | None = None) -> Key: if kid is None and len(self.keys) == 1: return self.keys[0] keys = [key for key in self.keys if key.kid == kid] if parameters: keys = list(_filter_keys_by_parameters(keys, parameters)) if keys: return keys[0] raise InvalidKeyIdError(f"No key for kid: '{kid}'") def pick_random_key(self, algorithm: str | None = None, parameters: KeyParameters | None = None) -> t.Optional[Key]: key_types = self.algorithm_keys.get(algorithm) if algorithm else None if key_types: keys = [k for k in self.keys if k.key_type in key_types] else: keys = self.keys if parameters: keys = list(_filter_keys_by_parameters(keys, parameters)) 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) def _filter_keys_by_parameters(keys: list[Key], parameters: KeyParameters) -> t.Generator[Key]: _use = parameters.get("use") _alg = parameters.get("alg") for key in keys: designed_use = key.get("use") if designed_use and _use and designed_use != _use: continue designed_alg = key.get("alg") if designed_alg and _alg and designed_alg != _alg: continue yield key authlib-joserfc-aae7743/src/joserfc/_rfc7515/000077500000000000000000000000001511744432500206735ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7515/__init__.py000066400000000000000000000000001511744432500227720ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7515/compact.py000066400000000000000000000030021511744432500226660ustar00rootroot00000000000000import typing as t from .model import JWSAlgModel, CompactSignature from ..errors import ( DecodeError, MissingAlgorithmError, ) from ..util import ( json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) __all__ = [ "sign_compact", "verify_compact", "detach_compact_content", "decode_header", ] 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 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) -> dict[str, t.Any]: try: protected: dict[str, t.Any] = json_b64decode(header_segment) if "alg" not in protected: raise MissingAlgorithmError() except (TypeError, ValueError): raise DecodeError("Invalid header") return protected authlib-joserfc-aae7743/src/joserfc/_rfc7515/json.py000066400000000000000000000121321511744432500222150ustar00rootroot00000000000000from __future__ import annotations import typing as t import copy from .model import ( HeaderMember, GeneralJSONSignature, FlattenedJSONSignature, ) from .types import ( JSONSignatureDict, GeneralJSONSerialization, FlattenedJSONSerialization, ) from .registry import JWSRegistry from ..registry import reject_unprotected_crit_header from ..util import ( to_bytes, json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) from ..errors import DecodeError __all__ = [ "FindKey", "sign_general_json", "sign_flattened_json", "sign_json_member", "extract_general_json", "verify_general_json", "verify_flattened_json", "detach_json_content", ] FindKey = t.Callable[[HeaderMember], t.Any] def sign_general_json( members: list[HeaderMember], payload: bytes, registry: JWSRegistry, find_key: FindKey, ) -> GeneralJSONSerialization: payload_segment = urlsafe_b64encode(payload) signatures: list[JSONSignatureDict] = [ sign_json_member(payload_segment, member, registry, find_key) for member in members ] return { "payload": payload_segment.decode("utf-8"), "signatures": signatures, } def sign_flattened_json( member: HeaderMember, payload: bytes, registry: JWSRegistry, find_key: FindKey, ) -> FlattenedJSONSerialization: payload_segment = urlsafe_b64encode(payload) signature = sign_json_member(payload_segment, member, registry, find_key) data: FlattenedJSONSerialization = {"payload": payload_segment.decode("utf-8"), **signature} return data def sign_json_member( payload_segment: bytes, member: HeaderMember, registry: JWSRegistry, find_key: FindKey ) -> JSONSignatureDict: reject_unprotected_crit_header(member.header) headers = member.headers() registry.check_header(headers) alg = registry.get_alg(headers["alg"]) key = find_key(member) alg.check_key(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, registry: JWSRegistry) -> GeneralJSONSignature: payload_segment: bytes = value["payload"].encode("utf-8") registry.validate_payload_size(payload_segment) try: payload = urlsafe_b64decode(payload_segment) except (TypeError, ValueError): raise DecodeError("Invalid payload") signatures: list[JSONSignatureDict] = value["signatures"] members = [__signature_to_member(sig, registry) for sig in signatures] obj = GeneralJSONSignature(members, payload) obj.signatures = signatures obj.segments = {"payload": payload_segment} return obj def __signature_to_member(sig: JSONSignatureDict, registry: JWSRegistry) -> HeaderMember: member = HeaderMember() if "protected" in sig: protected_segment = to_bytes(sig["protected"]) registry.validate_header_size(protected_segment) 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: reject_unprotected_crit_header(member.header) headers = member.headers() registry.check_header(headers) alg = registry.get_alg(headers["alg"]) key = find_key(member) alg.check_key(key) if "protected" in signature: protected_segment = to_bytes(signature["protected"]) else: protected_segment = b"" signature_segment = to_bytes(signature["signature"]) registry.validate_signature_size(signature_segment) sig = urlsafe_b64decode(signature_segment) signing_input = b".".join([protected_segment, payload_segment]) return alg.verify(signing_input, sig, key) def detach_json_content(value: dict[str, t.Any]) -> 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 authlib-joserfc-aae7743/src/joserfc/_rfc7515/model.py000066400000000000000000000102561511744432500223510ustar00rootroot00000000000000from __future__ import annotations from typing import Any, ClassVar, Literal from abc import ABCMeta, abstractmethod from .types import SegmentsDict, JSONSignatureDict from ..errors import InvalidKeyTypeError from ..registry import Header __all__ = [ "HeaderMember", "CompactSignature", "FlattenedJSONSignature", "GeneralJSONSignature", "JWSAlgModel", ] 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.header: rv.update(self.header) # protected header is preferred if self.protected: rv.update(self.protected) 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): #: protected header self.protected = protected #: payload content in bytes self.payload = payload self.segments: SegmentsDict = {} def headers(self) -> Header: """Returns protected header values in dict.""" 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: HeaderMember = member #: payload content in bytes self.payload: bytes = payload self.signature: JSONSignatureDict | None = None self.segments: SegmentsDict = {} @property def members(self) -> list[HeaderMember]: """A list of header members. For flattened JSON serialization, there will be only one header member.""" return [self.member] def headers(self) -> Header: """Header values in dict.""" 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: list[HeaderMember] = members #: payload content in bytes self.payload: bytes = 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 security_warning: str | None = None key_type = "oct" algorithm_type: Literal["JWS"] = "JWS" algorithm_location = "sig" algorithm_security = 0 def check_key(self, key: Any) -> None: key.check_use("sig") if key.key_type != self.key_type: raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires '{self.key_type}' key") key.check_alg(self.name) @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 """ authlib-joserfc-aae7743/src/joserfc/_rfc7515/registry.py000066400000000000000000000141641511744432500231230ustar00rootroot00000000000000from __future__ import annotations import warnings from typing import Any from enum import Enum from .model import JWSAlgModel from ..errors import ( JoseError, UnsupportedAlgorithmError, SecurityWarning, ExceededSizeError, ) from ..registry import ( JWS_HEADER_REGISTRY, Header, HeaderRegistryDict, check_registry_header, check_crit_header, check_supported_header, ) from .._keys import KeySet __all__ = [ "JWSRegistry", "construct_registry", "default_registry", ] 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 """ class Strategy(Enum): #: find the recommended algorithm RECOMMENDED = 1 #: find the most secure algorithm SECURITY = 2 default_header_registry: HeaderRegistryDict = JWS_HEADER_REGISTRY algorithms: dict[str, JWSAlgModel] = {} recommended: list[str] = [] #: max header content's size in bytes max_header_length: int = 512 #: max payload content's size in bytes max_payload_length: int = 8000 #: max signature's size in bytes max_signature_length: int = 1024 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") alg = self.algorithms[name] if alg.security_warning: warnings.warn(alg.security_warning, SecurityWarning) return alg def check_header(self, header: Header) -> None: """Check and validate the fields in header part of a JWS object.""" check_crit_header(self.header_registry, header) check_registry_header(self.header_registry, header) if self.strict_check_header: check_supported_header(self.header_registry, header) def validate_header_size(self, header: bytes) -> None: if header and len(header) > self.max_header_length: raise ExceededSizeError(f"Header size exceeds {self.max_header_length} bytes.") def validate_payload_size(self, payload: bytes) -> None: if payload and len(payload) > self.max_payload_length: raise ExceededSizeError(f"Payload size exceeds {self.max_payload_length} bytes.") def validate_signature_size(self, signature: bytes) -> None: if len(signature) > self.max_signature_length: raise ExceededSizeError(f"Signature of exceeds {self.max_signature_length} bytes.") @classmethod def guess_algorithm(cls, key: Any, strategy: Strategy) -> JWSAlgModel | None: """Guess the JWS algorithm for a given key. :param key: key instance or a KeySet :param strategy: the strategy for guessing the JWS algorithm """ if strategy == cls.Strategy.RECOMMENDED: algorithms = cls.filter_algorithms(key, cls.recommended) elif strategy == cls.Strategy.SECURITY: names = list(cls.algorithms.keys()) algorithms = cls.filter_algorithms(key, names) # sort by security level algorithms.sort(key=lambda alg: alg.algorithm_security, reverse=True) else: raise NotImplementedError(f"Unknown algorithm strategy '{strategy}'") if algorithms: return algorithms[0] else: return None @classmethod def guess_alg(cls, key: Any, strategy: Strategy) -> str | None: # pragma: no cover warnings.warn("Please use guess_algorithm(key, strategy)", DeprecationWarning) alg = cls.guess_algorithm(key, strategy) if alg: return alg.name return None @classmethod def filter_algorithms(cls, key: Any, names: list[str] | None = None) -> list[JWSAlgModel]: """Filter JWS algorithms based on the given algorithm names. :param key: a key instance or a KeySet :param names: list of algorithm names """ if names is None: names = list(cls.algorithms.keys()) rv: list[JWSAlgModel] = [] if isinstance(key, KeySet): for k in key.keys: for alg in cls.filter_algorithms(k, names): if alg not in rv: rv.append(alg) return rv for name in names: alg = cls.algorithms[name] try: alg.check_key(key) rv.append(alg) except JoseError: pass return rv #: 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 authlib-joserfc-aae7743/src/joserfc/_rfc7515/types.py000066400000000000000000000013621511744432500224130ustar00rootroot00000000000000import 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: list[JSONSignatureDict] @t.final class FlattenedJSONSerialization(t.TypedDict, total=False): payload: str protected: str header: Header signature: str authlib-joserfc-aae7743/src/joserfc/_rfc7516/000077500000000000000000000000001511744432500206745ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7516/__init__.py000066400000000000000000000000001511744432500227730ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7516/compact.py000066400000000000000000000043271511744432500227020ustar00rootroot00000000000000from .models import CompactEncryption, Recipient from .registry import JWERegistry from .._keys import Key from ..errors import ( MissingAlgorithmError, MissingEncryptionError, DecodeError, ) from ..util import ( json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) __all__ = [ "represent_compact", "extract_compact", ] 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, registry: JWERegistry) -> 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 registry.validate_protected_header_size(header_segment) registry.validate_encrypted_key_size(ek_segment) registry.validate_initialization_vector_size(iv_segment) registry.validate_ciphertext_size(ciphertext_segment) registry.validate_auth_tag_size(tag_segment) 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 authlib-joserfc-aae7743/src/joserfc/_rfc7516/json.py000066400000000000000000000114031511744432500222160ustar00rootroot00000000000000from __future__ import annotations import typing as t from .models import ( BaseJSONEncryption, GeneralJSONEncryption, FlattenedJSONEncryption, Recipient, ) from .registry import JWERegistry from .types import ( JSONRecipientDict, GeneralJSONSerialization, FlattenedJSONSerialization, ) from ..util import ( to_bytes, to_str, json_b64encode, json_b64decode, urlsafe_b64encode, urlsafe_b64decode, ) from .._keys import Key __all__ = [ "represent_general_json", "represent_flattened_json", "extract_general_json", "extract_flattened_json", ] def represent_general_json(obj: GeneralJSONEncryption) -> GeneralJSONSerialization: data: GeneralJSONSerialization = __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 def represent_flattened_json(obj: FlattenedJSONEncryption) -> FlattenedJSONSerialization: data: FlattenedJSONSerialization = __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 def __represent_json_serialization(obj: BaseJSONEncryption) -> t.Any: data: dict[str, t.Any] = { "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, registry: JWERegistry) -> GeneralJSONEncryption: protected_segment = to_bytes(data["protected"]) registry.validate_protected_header_size(protected_segment) protected = json_b64decode(protected_segment) unprotected = data.get("unprotected") base64_segments, bytes_segments, aad = __extract_segments(data, registry) obj = GeneralJSONEncryption(protected, None, unprotected, aad) obj.base64_segments = base64_segments obj.bytes_segments = bytes_segments for item in data["recipients"]: recipient = __extract_recipient(obj, item, registry) obj.recipients.append(recipient) return obj def extract_flattened_json(data: FlattenedJSONSerialization, registry: JWERegistry) -> FlattenedJSONEncryption: protected_segment = to_bytes(data["protected"]) registry.validate_protected_header_size(protected_segment) protected = json_b64decode(protected_segment) unprotected = data.get("unprotected") base64_segments, bytes_segments, aad = __extract_segments(data, registry) obj = FlattenedJSONEncryption(protected, None, unprotected, aad) obj.base64_segments = base64_segments obj.bytes_segments = bytes_segments recipient = __extract_recipient(obj, data, registry) obj.recipients.append(recipient) return obj def __extract_segments( data: t.Union[GeneralJSONSerialization, FlattenedJSONSerialization], registry: JWERegistry, ) -> tuple[dict[str, bytes], dict[str, bytes], t.Optional[bytes]]: base64_segments: dict[str, bytes] = { "iv": to_bytes(data["iv"]), "ciphertext": to_bytes(data["ciphertext"]), "tag": to_bytes(data["tag"]), } registry.validate_initialization_vector_size(base64_segments["iv"]) registry.validate_ciphertext_size(base64_segments["ciphertext"]) registry.validate_auth_tag_size(base64_segments["tag"]) bytes_segments: 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 def __extract_recipient( obj: FlattenedJSONEncryption | GeneralJSONEncryption, data: FlattenedJSONSerialization | JSONRecipientDict, registry: JWERegistry, ) -> Recipient[Key]: recipient: Recipient[Key] = Recipient(obj, data.get("header")) if "encrypted_key" in data: ek_segment = to_bytes(data["encrypted_key"]) registry.validate_encrypted_key_size(ek_segment) recipient.encrypted_key = urlsafe_b64decode(ek_segment) return recipient authlib-joserfc-aae7743/src/joserfc/_rfc7516/message.py000066400000000000000000000237401511744432500227000ustar00rootroot00000000000000import 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, ) __all__ = [ "EncryptionData", "perform_encrypt", "perform_decrypt", ] 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: list[Recipient[t.Any]], registry: JWERegistry ) -> tuple[bytes, list[tuple[JWEKeyAgreement, Recipient[t.Any]]]]: cek: bytes = b"" delayed_tasks: list[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: list[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 authlib-joserfc-aae7743/src/joserfc/_rfc7516/models.py000066400000000000000000000250211511744432500225310ustar00rootroot00000000000000from __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 __all__ = [ "Recipient", "CompactEncryption", "BaseJSONEncryption", "GeneralJSONEncryption", "FlattenedJSONEncryption", "JWEEncModel", "JWEZipModel", "KeyManagement", "JWEDirectEncryption", "JWEKeyEncryption", "JWEKeyWrapping", "JWEKeyAgreement", "JWEAlgModel", ] 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 = {} if isinstance(self.__parent, BaseJSONEncryption) and self.__parent.unprotected: rv.update(self.__parent.unprotected) if self.header: rv.update(self.header) rv.update(self.__parent.protected) 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: dict[str, bytes] = {} # store the decoded segments self.base64_segments: dict[str, bytes] = {} # store the encoded segments def headers(self) -> Header: """Returns the protected header values in dict.""" 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: 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: dict[str, bytes] = {} # store the decoded segments self.base64_segments: 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: int | None = None key_types: list[str] security_warning: str | None = None 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] authlib-joserfc-aae7743/src/joserfc/_rfc7516/registry.py000066400000000000000000000141211511744432500231150ustar00rootroot00000000000000from __future__ import annotations import warnings import typing as t from .models import JWEAlgModel, JWEEncModel, JWEZipModel from ..errors import ( UnsupportedAlgorithmError, SecurityWarning, ExceededSizeError, ) from ..registry import ( Header, HeaderRegistryDict, JWE_HEADER_REGISTRY, check_supported_header, check_registry_header, check_crit_header, ) __all__ = [ "JWEAlgorithm", "JWERegistry", "default_registry", ] JWEAlgorithm = t.Union[JWEAlgModel, JWEEncModel, JWEZipModel] AlgorithmsDict = t.TypedDict( "AlgorithmsDict", { "alg": dict[str, JWEAlgModel], "enc": dict[str, JWEEncModel], "zip": 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: AlgorithmsDict = { "alg": {}, "enc": {}, "zip": {}, } recommended: t.ClassVar[list[str]] = [] #: max protected header content's size in bytes max_protected_header_length: int = 1024 #: max encrypted key's size in bytes max_encrypted_key_length: int = 1024 #: max initialization vector's size in bytes max_initialization_vector_length: int = 64 #: max ciphertext's size in bytes max_ciphertext_length: int = 65536 # 64KB #: max auth tag's size in bytes max_auth_tag_length: int = 64 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(self.header_registry, header) check_registry_header(self.header_registry, header) alg = self.get_alg(header["alg"]) if alg.more_header_registry: check_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 validate_protected_header_size(self, header: bytes) -> None: if header and len(header) > self.max_protected_header_length: raise ExceededSizeError(f"Header size exceeds {self.max_protected_header_length} bytes.") def validate_encrypted_key_size(self, ek: bytes) -> None: if ek and len(ek) > self.max_encrypted_key_length: raise ExceededSizeError(f"Encrypted key size exceeds {self.max_encrypted_key_length} bytes.") def validate_initialization_vector_size(self, iv: bytes) -> None: if iv and len(iv) > self.max_initialization_vector_length: raise ExceededSizeError( f"Initialization vector size exceeds {self.max_initialization_vector_length} bytes." ) def validate_ciphertext_size(self, ciphertext: bytes) -> None: if ciphertext and len(ciphertext) > self.max_ciphertext_length: raise ExceededSizeError(f"Ciphertext size exceeds {self.max_ciphertext_length} bytes.") def validate_auth_tag_size(self, tag: bytes) -> None: if tag and len(tag) > self.max_auth_tag_length: raise ExceededSizeError(f"Auth tag size exceeds {self.max_auth_tag_length} bytes.") 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) alg: JWEAlgModel = registry[name] if alg.security_warning: warnings.warn(alg.security_warning, SecurityWarning) return alg 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() authlib-joserfc-aae7743/src/joserfc/_rfc7516/types.py000066400000000000000000000012321511744432500224100ustar00rootroot00000000000000import typing as t __all__ = [ "JSONRecipientDict", "FlattenedJSONSerialization", "GeneralJSONSerialization", ] class JSONRecipientDict(t.TypedDict, total=False): header: dict[str, t.Any] encrypted_key: str class GeneralJSONSerialization(t.TypedDict, total=False): protected: str unprotected: dict[str, t.Any] iv: str aad: str ciphertext: str tag: str recipients: list[JSONRecipientDict] class FlattenedJSONSerialization(t.TypedDict, total=False): protected: str unprotected: dict[str, t.Any] iv: str aad: str ciphertext: str tag: str header: dict[str, t.Any] encrypted_key: str authlib-joserfc-aae7743/src/joserfc/_rfc7517/000077500000000000000000000000001511744432500206755ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7517/__init__.py000066400000000000000000000000001511744432500227740ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7517/models.py000066400000000000000000000277401511744432500225440ustar00rootroot00000000000000from __future__ import annotations import typing as t from collections.abc import KeysView from abc import ABCMeta, abstractmethod from .types import DictKey, AnyKey, KeyParameters from .._rfc7638 import calculate_thumbprint from .._rfc9278 import concat_thumbprint_uri from ..registry import ( KeyParameterRegistryDict, JWK_PARAMETER_REGISTRY, KeyOperationRegistryDict, JWK_OPERATION_REGISTRY, ) from ..util import to_bytes from ..errors import ( KeyParameterError, 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[dict[str, 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.Any = None) -> t.Any: pass @staticmethod def as_bytes( key: GenericKey, encoding: t.Literal["PEM", "DER"] | None = None, private: bool | None = None, password: str | None = 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 KeyParameterError(f"'{k}' is required") if k in dict_key: try: registry[k].validate(dict_key[k]) except ValueError as error: raise KeyParameterError(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 KeyParameterError("'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.Literal["sha256", "sha384", "sha512"] = "sha256" def __init__( self, raw_value: NativePrivateKey | NativePublicKey, original_value: t.Any, parameters: KeyParameters | None = 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) -> NativePrivateKey | None: 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") data = {key: self.dict_value[key] for key in fields} return calculate_thumbprint(data, self.thumbprint_digest_method) def thumbprint_uri(self) -> str: """Call this method will generate the thumbprint URI defined in RFC9278.""" value = self.thumbprint() return concat_thumbprint_uri(value, self.thumbprint_digest_method) def as_dict(self, private: bool | None = 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". Values defined by this specification are: - "sig" (signature) - "enc" (encryption) Other values MAY be used. The "use" value is a case-sensitive string. Use of the "use" member is OPTIONAL, unless the application requires its presence. :param use: this key is used for, e.g. "sig", "enc" :raise UnsupportedKeyUseError: if this key is not designed for the given use """ 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: if this key is not designed for the given algorithm """ 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: if the operation is not supported by this key. """ 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: KeyParameters | None = None, password: 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: KeyParameters | None = 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.Literal["PEM", "DER"] | None = None, private: bool | None = None, password: str | None = None, ) -> bytes: return self.binding.as_bytes(self, encoding, private, password) def as_pem(self, private: bool | None = None, password: str | None = None) -> bytes: return self.as_bytes(private=private, password=password) def as_der(self, private: bool | None = None, password: str | None = 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 authlib-joserfc-aae7743/src/joserfc/_rfc7517/pem.py000066400000000000000000000112601511744432500220300ustar00rootroot00000000000000from __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 .models import NativeKeyBinding, GenericKey from .types import DictKey from ..errors import InvalidKeyTypeError from ..util import to_bytes def import_from_ssh_key(raw: bytes) -> Any: return load_ssh_public_key(raw) def import_from_pem_key(raw: bytes, password: bytes | None = None) -> Any: key: Any if b"OPENSSH PRIVATE" in raw: key = load_ssh_private_key(raw, password=password) elif b"PUBLIC" in raw: key = load_pem_public_key(raw) elif b"PRIVATE" in raw: key = load_pem_private_key(raw, password=password) elif b"CERTIFICATE" in raw: cert = load_pem_x509_certificate(raw) return cert.public_key() else: try: key = load_der_private_key(raw, password=password) except ValueError: key = load_der_public_key(raw) 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(f"Invalid encoding: {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): key_type: str ssh_type: bytes _cryptography_key_types: Any @classmethod def check_ssh_type(cls, value: bytes) -> bool: return value.startswith(cls.ssh_type) @classmethod def check_cryptography_key(cls, native_key: Any) -> bool: return isinstance(native_key, cls._cryptography_key_types) @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 cls.check_ssh_type(value): return import_from_ssh_key(value) if password is not None: password = to_bytes(password) key = import_from_pem_key(value, password) if not cls.check_cryptography_key(key): raise InvalidKeyTypeError(f"Not a key of: '{cls.key_type}'") return key @staticmethod def as_bytes( key: GenericKey, encoding: Literal["PEM", "DER"] | None = None, private: bool | None = False, password: Any | None = None, ) -> bytes: if private is None: private = key.is_private if private: return dump_pem_key(key.private_key, encoding, private, password) else: return dump_pem_key(key.public_key, encoding, 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 authlib-joserfc-aae7743/src/joserfc/_rfc7517/types.py000066400000000000000000000007701511744432500224170ustar00rootroot00000000000000import typing as t __all__ = ["DictKey", "AnyKey", "KeyParameters"] #: JSON Web Key in dict DictKey = dict[str, t.Union[str, 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": list[str], "alg": str, "kid": str, "x5u": str, "x5c": list[str], "x5t": str, "x5t#S256": str, }, total=False, ) authlib-joserfc-aae7743/src/joserfc/_rfc7518/000077500000000000000000000000001511744432500206765ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7518/__init__.py000066400000000000000000000000001511744432500227750ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7518/derive_key.py000066400000000000000000000026341511744432500234030ustar00rootroot00000000000000from __future__ import annotations import struct from cryptography.hazmat.primitives import hashes 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, ) 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 authlib-joserfc-aae7743/src/joserfc/_rfc7518/ec_key.py000066400000000000000000000215111511744432500225070ustar00rootroot00000000000000from __future__ import annotations import typing as t from functools import cached_property from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric.ec import ( generate_private_key, derive_private_key, ECDH, EllipticCurvePublicKey, EllipticCurvePrivateKey, EllipticCurvePrivateNumbers, EllipticCurvePublicNumbers, EllipticCurve, SECP256R1, SECP384R1, SECP521R1, ) from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from ..errors import InvalidExchangeKeyError, InvalidKeyCurveError from .._rfc7517.models import CurveKey from .._rfc7517.pem import CryptographyBinding from .._rfc7517.types import KeyParameters, AnyKey from ..util import base64_to_int, int_to_base64, to_bytes from ..registry import KeyParameter __all__ = ["ECKey"] ECDictKey = t.TypedDict( "ECDictKey", { "crv": str, "x": str, "y": str, "d": str, # optional }, total=False, ) class ECBinding(CryptographyBinding): key_type = "EC" ssh_type = b"ecdsa-sha2-" _cryptography_key_types = (EllipticCurvePrivateKey, EllipticCurvePublicKey) _dss_curves: dict[str, t.Type[EllipticCurve]] = {} _curves_dss: 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 InvalidKeyCurveError(f"Invalid crv value: '{name}'") curve = cls._dss_curves[name]() return generate_private_key(curve=curve) @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() @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() @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), } # 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) 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 import_key( cls: t.Any, value: AnyKey | EllipticCurvePrivateKey | EllipticCurvePublicKey, parameters: KeyParameters | None = None, password: t.Any = None, ) -> "ECKey": key: ECKey if isinstance(value, (EllipticCurvePrivateKey, EllipticCurvePublicKey)): key = cls(value, value, parameters) else: key = super(ECKey, cls).import_key(value, parameters, password) return key @classmethod def generate_key( cls: t.Type["ECKey"], 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) return _wrap_key(cls, raw_key, private, auto_kid, parameters) @classmethod def derive_key( cls: t.Type["ECKey"], secret: bytes | str, crv: str = "P-256", parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, kdf_name: t.Literal["HKDF", "PBKDF2"] = "HKDF", kdf_options: dict[str, t.Any] | None = None, ) -> "ECKey": """ Generate an elliptic curve cryptographic key derived from a secret input using a key derivation function (KDF). This allows the creation of deterministic elliptic curve keys based on given input data, curve specification, and KDF options. :param secret: The input secret used for key derivation :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 :param kdf_name: Key derivation function name :param kdf_options: Additional options for the KDF """ try: curve_class = cls.binding._dss_curves[crv] except KeyError: raise InvalidKeyCurveError(f"Invalid crv value: '{crv}'") curve = curve_class() length = (curve.group_order.bit_length() + 7) // 8 * 2 if kdf_options is None: kdf_options = {} algorithm = kdf_options.pop("algorithm", None) if algorithm is None: algorithm = hashes.SHA256() kdf_options.setdefault("salt", to_bytes(f"joserfc:EC:{kdf_name}:{crv}")) if kdf_name == "HKDF": kdf_options.setdefault("info", b"") hkdf = HKDF( algorithm=algorithm, length=length, **kdf_options, ) seed = hkdf.derive(to_bytes(secret)) elif kdf_name == "PBKDF2": kdf_options.setdefault("iterations", 100000) pbkdf2 = PBKDF2HMAC( algorithm=algorithm, length=length, **kdf_options, ) seed = pbkdf2.derive(to_bytes(secret)) else: raise ValueError(f"Invalid kdf value: '{kdf_name}'") d = int.from_bytes(seed, "big") % curve.group_order raw_key = derive_private_key(d, curve) return _wrap_key(cls, raw_key, private, auto_kid, parameters) def _wrap_key( cls: t.Type["ECKey"], raw_key: EllipticCurvePrivateKey, private: bool, auto_kid: bool, parameters: KeyParameters | None = None, ) -> ECKey: 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 authlib-joserfc-aae7743/src/joserfc/_rfc7518/jwe_algs.py000066400000000000000000000306201511744432500230440ustar00rootroot00000000000000from __future__ import annotations import secrets from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes 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 DirectAlgEncryption(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 RSAAlgKeyEncryption(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 AESAlgKeyWrapping(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) def unwrap_cek(self, ek: bytes, key: bytes) -> bytes: self.check_op_key(key) try: cek = aes_key_unwrap(key, ek) 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 AESGCMAlgKeyWrapping(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)) 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)) 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 ECDHESAlgKeyAgreement(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 PBES2HSAlgKeyEncryption(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, ) 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) RSA1_5 = RSAAlgKeyEncryption("RSA1_5", "RSAES-PKCS1-v1_5", padding.PKCS1v15()) RSA1_5.security_warning = 'JWE algorithm "RSA1_5" is deprecated, via draft-ietf-jose-deprecate-none-rsa15-02' A128KW = AESAlgKeyWrapping(128, True) # A128KW, Recommended A192KW = AESAlgKeyWrapping(192) # A192KW A256KW = AESAlgKeyWrapping(256, True) # A256KW, Recommended #: https://www.rfc-editor.org/rfc/rfc7518#section-4.1 JWE_ALG_MODELS: list[JWEAlgModel] = [ RSA1_5, RSAAlgKeyEncryption( "RSA-OAEP", "RSAES OAEP using default parameters", padding.OAEP(padding.MGF1(hashes.SHA1()), hashes.SHA1(), None), True, ), # Recommended+ RSAAlgKeyEncryption( "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, DirectAlgEncryption(), # dir, Recommended ECDHESAlgKeyAgreement(None), # ECDH-ES, Recommended+ ECDHESAlgKeyAgreement(A128KW), # ECDH-ES+A128KW, Recommended ECDHESAlgKeyAgreement(A192KW), # ECDH-ES+A192KW ECDHESAlgKeyAgreement(A256KW), # ECDH-ES+A256KW, Recommended AESGCMAlgKeyWrapping(128), # A128GCMKW AESGCMAlgKeyWrapping(192), # A192GCMKW AESGCMAlgKeyWrapping(256), # A256GCMKW PBES2HSAlgKeyEncryption(256, A128KW), # PBES2-HS256+A128KW PBES2HSAlgKeyEncryption(384, A192KW), # PBES2-HS384+A192KW PBES2HSAlgKeyEncryption(512, A256KW), # PBES2-HS512+A256KW ] # compatible alias DirectAlgModel = DirectAlgEncryption AESAlgModel = AESAlgKeyWrapping ECDHESAlgModel = ECDHESAlgKeyAgreement AESGCMAlgModel = AESGCMAlgKeyWrapping PBES2HSAlgModel = PBES2HSAlgKeyEncryption authlib-joserfc-aae7743/src/joserfc/_rfc7518/jwe_encs.py000066400000000000000000000103271511744432500230500ustar00rootroot00000000000000""" joserfc._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.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() # noqa: S5542 # Safe: JWE A128CBC-HS256 uses CBC + HMAC (Encrypt-then-MAC) cipher = Cipher(AES(ekey), CBC(iv)) 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") # noqa: S5542 # Safe: JWE A128CBC-HS256 uses CBC + HMAC (Encrypt-then-MAC) cipher = Cipher(AES(dkey), CBC(iv)) 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)) 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)) 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 ] authlib-joserfc-aae7743/src/joserfc/_rfc7518/jwe_zips.py000066400000000000000000000020441511744432500231020ustar00rootroot00000000000000from __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()] authlib-joserfc-aae7743/src/joserfc/_rfc7518/jws_algs.py000066400000000000000000000155701511744432500230710ustar00rootroot00000000000000""" joserfc._rfc7518 ~~~~~~~~~~~~~~~~ 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 ..errors import InvalidKeyCurveError from .oct_key import OctKey from .rsa_key import RSAKey from .ec_key import ECKey from .util import encode_int, decode_int class NoneAlgorithm(JWSAlgModel): name = "none" description = "No digital signature or MAC performed" security_warning = 'JWS algorithm "none" is deprecated, via draft-ietf-jose-deprecate-none-rsa15-02' 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 HMACAlgorithm(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}") self.algorithm_security = 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 RSAAlgorithm(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}") self.algorithm_security = 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 ESAlgorithm(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}") self.algorithm_security = sha_type def check_key(self, key: ECKey) -> None: super().check_key(key) if key.curve_name != self.curve: raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed") def sign(self, msg: bytes, key: ECKey) -> bytes: 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: 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 RSAPSSAlgorithm(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) self.algorithm_security = 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 JWS_ALGORITHMS: list[JWSAlgModel] = [ NoneAlgorithm(), # none HMACAlgorithm(256, True), # HS256 HMACAlgorithm(384), # HS384 HMACAlgorithm(512), # HS512 RSAAlgorithm(256, True), # RS256 RSAAlgorithm(384), # RS384 RSAAlgorithm(512), # RS512 ESAlgorithm("ES256", "P-256", 256, True), ESAlgorithm("ES384", "P-384", 384), ESAlgorithm("ES512", "P-521", 512), RSAPSSAlgorithm(256), # PS256 RSAPSSAlgorithm(384), # PS384 RSAPSSAlgorithm(512), # PS512 ] # compatible NoneAlgModel = NoneAlgorithm HMACAlgModel = HMACAlgorithm RSAAlgModel = RSAAlgorithm ECAlgModel = ESAlgorithm RSAPSSAlgModel = RSAPSSAlgorithm authlib-joserfc-aae7743/src/joserfc/_rfc7518/oct_key.py000066400000000000000000000056311511744432500227120ustar00rootroot00000000000000from __future__ import annotations from typing import Any import secrets import warnings from ..errors import SecurityWarning 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, AnyKey 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 should not be used as an oct key", SecurityWarning) 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 import_key( cls: Any, value: AnyKey, parameters: KeyParameters | None = None, password: Any = None, ) -> "OctKey": key: OctKey = super(OctKey, cls).import_key(value, parameters, password) if len(key.raw_value) < 14: # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final warnings.warn("Key size should be >= 112 bits", SecurityWarning) return key @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") if key_size < 112: # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final warnings.warn("Key size should be >= 112 bits", SecurityWarning) raw_key = secrets.token_bytes(key_size // 8) key: OctKey = cls(raw_key, raw_key, parameters) if auto_kid: key.ensure_kid() return key authlib-joserfc-aae7743/src/joserfc/_rfc7518/rsa_key.py000066400000000000000000000154641511744432500227170ustar00rootroot00000000000000from __future__ import annotations import warnings import typing as t 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 ..registry import KeyParameter from ..errors import SecurityWarning, KeyParameterError from .._rfc7517.models import AsymmetricKey from .._rfc7517.pem import CryptographyBinding from .._rfc7517.types import KeyParameters, AnyKey from ..util import int_to_base64, base64_to_int RSADictKey = t.TypedDict( "RSADictKey", { "n": str, "e": str, "d": str, "p": str, "q": str, "dp": str, "dq": str, "qi": str, }, total=False, ) class RSABinding(CryptographyBinding): key_type = "RSA" ssh_type = b"ssh-rsa" _cryptography_key_types = (RSAPrivateKey, RSAPublicKey) @staticmethod def generate_private_key(size: int) -> RSAPrivateKey: return generate_private_key(public_exponent=65537, key_size=size) @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() @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() @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 import_key( cls: t.Any, value: AnyKey | RSAPrivateKey | RSAPublicKey, parameters: KeyParameters | None = None, password: t.Any = None, ) -> "RSAKey": key: RSAKey if isinstance(value, (RSAPrivateKey, RSAPublicKey)): key = cls(value, value, parameters) else: key = super(RSAKey, cls).import_key(value, parameters, password) if key.raw_value.key_size < 2048: # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final warnings.warn("Key size should be >= 2048 bits", SecurityWarning) return key @classmethod def generate_key( cls: t.Type["RSAKey"], 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 % 8 != 0: raise ValueError("A bit size must be a multiple of 8") if key_size < 2048: # https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final warnings.warn("Key size should be >= 2048 bits", SecurityWarning) raw_key = cls.binding.generate_private_key(key_size) 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 KeyParameterError("RSA key must include all parameters if any are present besides d") return False authlib-joserfc-aae7743/src/joserfc/_rfc7518/util.py000066400000000000000000000004521511744432500222260ustar00rootroot00000000000000import 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) authlib-joserfc-aae7743/src/joserfc/_rfc7519/000077500000000000000000000000001511744432500206775ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7519/__init__.py000066400000000000000000000000001511744432500227760ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7519/claims.py000066400000000000000000000142361511744432500225270ustar00rootroot00000000000000from __future__ import annotations import time import json import datetime import calendar from json import JSONEncoder from typing import TypedDict, Type, Any, Callable from ..util import to_bytes from ..errors import ( MissingClaimError, InvalidClaimError, ExpiredTokenError, InvalidTokenError, ) 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) #: 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 BaseClaimsRegistry: """Requesting "claims" for JWT with the given conditions.""" def __init__(self, **kwargs: ClaimsOption): self.options = kwargs @property def essential_keys(self) -> set[str]: """Returns the essential claim names.""" return {key for key in self.options if self.options[key].get("essential")} def check_value(self, claim_name: str, value: Any) -> None: """ Validates a given claim value based on predefined options. :param claim_name: The name of the claim to validate. :param value: The value of the claim to be validated. :raises InvalidClaimError: If the value does not meet the claim's validation requirements. """ option = self.options.get(claim_name) if not option: return allow_blank = option.get("allow_blank") if not allow_blank and value in (None, "", [], {}): raise InvalidClaimError(claim_name) option_values = option.get("values") if option_values is None: option_value = option.get("value") if option_value is not None: option_values = [option_value] if not option_values: return if isinstance(value, list): if not any(v in value for v in option_values): raise InvalidClaimError(claim_name) else: if value not in option_values: raise InvalidClaimError(claim_name) def validate(self, claims: dict[str, Any]) -> None: """ Validates the provided claims against specified requirements and checks. :param claims: A dictionary containing claims to validate. :raises InvalidClaimError: Raised if any claim fails validation. :raises MissingClaimError: Raised if one or more essential keys are missing. """ 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(BaseClaimsRegistry): """A claims registry for validating JWT claims. :param now: timestamp of "now" time :param leeway: leeway time in seconds :param kwargs: claims options """ def __init__(self, now: int | Callable[[], int] | None = None, leeway: int = 0, **kwargs: ClaimsOption) -> None: if now is None: now = _generate_now self._now = now self.leeway = leeway super().__init__(**kwargs) @property def now(self) -> int: """Returns the current timestamp.""" if callable(self._now): return self._now() return self._now 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)) def _generate_now() -> int: return int(time.time()) authlib-joserfc-aae7743/src/joserfc/_rfc7519/security.py000066400000000000000000000023661511744432500231270ustar00rootroot00000000000000from __future__ import annotations import re from typing import Any 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, ) def check_sensitive_data(claims: dict[str, Any]) -> None: """ Checks for sensitive data within a dictionary of claims and raises an error if any sensitive names or values are detected. :param claims: JWT claims to check for sensitive data :raises InsecureClaimError: if any sensitive names or values are detected """ 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) authlib-joserfc-aae7743/src/joserfc/_rfc7638/000077500000000000000000000000001511744432500207015ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7638/__init__.py000066400000000000000000000016521511744432500230160ustar00rootroot00000000000000import typing as t import json import hashlib from collections import OrderedDict from ..util import to_bytes, urlsafe_b64encode def calculate_thumbprint( value: dict[str, t.Any], digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256", ) -> str: """Calculate the thumbprint value of a Key, per RFC 7638. .. code-block:: python from joserfc import jwk jwk.thumbprint({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' """ sorted_fields = sorted(value.keys()) data = OrderedDict() for k in sorted_fields: data[k] = 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") authlib-joserfc-aae7743/src/joserfc/_rfc7797/000077500000000000000000000000001511744432500207075ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7797/__init__.py000066400000000000000000000000001511744432500230060ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc7797/compact.py000066400000000000000000000054461511744432500227200ustar00rootroot00000000000000from __future__ import annotations import re from typing import Any from ..util import ( to_bytes, to_str, json_b64encode, urlsafe_b64encode, urlsafe_b64decode, ) from .._rfc7515.model import JWSAlgModel, CompactSignature from .._rfc7515.compact import decode_header from .._rfc7515.registry import JWSRegistry, default_registry from ..errors import DecodeError from .util import is_rfc7797_enabled def sign_rfc7515_compact(obj: CompactSignature, alg: JWSAlgModel, key: Any) -> bytes: header_segment = json_b64encode(obj.headers()) signing_input = header_segment + b"." + obj.payload signature = urlsafe_b64encode(alg.sign(signing_input, key)) # if need to detach payload if __is_urlsafe_characters(obj.payload): out = signing_input + b"." + signature else: out = header_segment + b".." + signature return out def extract_rfc7515_compact( value: bytes, payload: bytes | str | None = None, registry: JWSRegistry | None = None ) -> CompactSignature: """Extract the JWS Compact Serialization from bytes to object. :param value: JWS in bytes :param payload: optional payload, required with detached content :param registry: optional JWSRegistry instance :raise DecodeError: when decoding fails """ parts = value.split(b".") if len(parts) != 3: raise DecodeError("Invalid JSON Web Signature") if registry is None: registry = default_registry header_segment, payload_segment, signature_segment = parts registry.validate_header_size(header_segment) registry.validate_signature_size(signature_segment) protected = decode_header(header_segment) if is_rfc7797_enabled(protected): if not payload_segment and payload: payload_segment = to_bytes(payload) payload = payload_segment else: if not payload_segment and payload: payload = to_bytes(payload) payload_segment = urlsafe_b64encode(payload) else: registry.validate_payload_size(payload_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 # 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))) authlib-joserfc-aae7743/src/joserfc/_rfc7797/json.py000066400000000000000000000035201511744432500222320ustar00rootroot00000000000000from __future__ import annotations from .._rfc7515.types import FlattenedJSONSerialization, JSONSignatureDict from .._rfc7515.model import HeaderMember, FlattenedJSONSignature from .._rfc7515.registry import JWSRegistry from .._rfc7515.json import sign_json_member, FindKey from ..util import to_bytes, json_b64decode, urlsafe_b64decode from ..errors import DecodeError from .util import is_rfc7797_enabled def sign_rfc7797_json( member: HeaderMember, payload: bytes, registry: JWSRegistry, find_key: FindKey, ) -> FlattenedJSONSerialization: signature = sign_json_member(payload, member, registry, find_key) data: FlattenedJSONSerialization = {"payload": payload.decode("utf-8"), **signature} return data def extract_rfc7797_json(value: FlattenedJSONSerialization, registry: JWSRegistry) -> FlattenedJSONSignature: if "protected" in value: protected_segment = to_bytes(value["protected"]) registry.validate_header_size(protected_segment) protected = json_b64decode(protected_segment) else: protected = None header = value.get("header") member = HeaderMember(protected, header) payload_segment: bytes = value["payload"].encode("utf-8") if is_rfc7797_enabled(member.headers()): payload = payload_segment else: registry.validate_payload_size(payload_segment) try: payload = urlsafe_b64decode(payload_segment) except (TypeError, ValueError): raise DecodeError("Invalid 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_segment} return obj authlib-joserfc-aae7743/src/joserfc/_rfc7797/util.py000066400000000000000000000006541511744432500222430ustar00rootroot00000000000000from ..registry import Header from ..errors import MissingCritHeaderError def is_rfc7797_enabled(header: Header) -> bool: if "b64" not in header: return False if header["b64"] is True: return False # https://datatracker.ietf.org/doc/html/rfc7797#section-6 crit = header.get("crit") if isinstance(crit, list) and "b64" in crit: return True raise MissingCritHeaderError("b64") authlib-joserfc-aae7743/src/joserfc/_rfc8037/000077500000000000000000000000001511744432500206735ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc8037/__init__.py000066400000000000000000000000001511744432500227720ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc8037/jws_eddsa.py000066400000000000000000000025061511744432500232130ustar00rootroot00000000000000from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey from ..errors import InvalidKeyTypeError from .._rfc7515.model import JWSAlgModel from .okp_key import OKPKey class EdDSAAlgorithm(JWSAlgModel): name = "EdDSA" description = "Edwards-curve Digital Signature Algorithm for JWS" key_type = "OKP" security_warning = "EdDSA is deprecated via RFC 9864" def sign(self, msg: bytes, key: OKPKey) -> bytes: op_key = key.get_op_key("sign") if not isinstance(op_key, (Ed25519PrivateKey, Ed448PrivateKey)): raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires 'Ed25519' or 'Ed448' OKP key") return op_key.sign(msg) def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool: op_key = key.get_op_key("verify") if not isinstance(op_key, (Ed25519PublicKey, Ed448PublicKey)): raise InvalidKeyTypeError(f"Algorithm '{self.name}' requires 'Ed25519' or 'Ed448' OKP key") try: op_key.verify(sig, msg) return True except InvalidSignature: return False EdDSA = EdDSAAlgorithm() # compatible EdDSAAlgModel = EdDSAAlgorithm authlib-joserfc-aae7743/src/joserfc/_rfc8037/okp_key.py000066400000000000000000000262501511744432500227130ustar00rootroot00000000000000from __future__ import annotations import typing as t from functools import cached_property from cryptography.hazmat.primitives import hashes 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 cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from .._rfc7517.models import CurveKey from .._rfc7517.types import KeyParameters, AnyKey from .._rfc7517.pem import CryptographyBinding from ..errors import InvalidExchangeKeyError, InvalidKeyCurveError from ..util import to_bytes, urlsafe_b64decode, urlsafe_b64encode from ..registry import KeyParameter LiteralCurves = t.Literal["Ed25519", "Ed448", "X25519", "X448"] PublicOKPKey = t.Union[Ed25519PublicKey, Ed448PublicKey, X25519PublicKey, X448PublicKey] PrivateOKPKey = t.Union[Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey] OKPDictKey = t.TypedDict( "OKPDictKey", { "crv": LiteralCurves, "x": str, "d": str, }, total=False, ) PUBLIC_KEYS_MAP: dict[str, t.Type[PublicOKPKey]] = { "Ed25519": Ed25519PublicKey, "Ed448": Ed448PublicKey, "X25519": X25519PublicKey, "X448": X448PublicKey, } PRIVATE_KEYS_MAP: dict[str, t.Type[PrivateOKPKey]] = { "Ed25519": Ed25519PrivateKey, "Ed448": Ed448PrivateKey, "X25519": X25519PrivateKey, "X448": X448PrivateKey, } OKP_SEED_SIZES: dict[LiteralCurves, int] = { "Ed25519": 32, "Ed448": 57, "X25519": 32, "X448": 56, } PrivateKeyTypes = (Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey) class OKPBinding(CryptographyBinding): key_type = "OKP" ssh_type = b"ssh-ed25519" _cryptography_key_types = ( Ed25519PublicKey, Ed25519PrivateKey, Ed448PublicKey, Ed448PrivateKey, X25519PublicKey, X25519PrivateKey, X448PublicKey, X448PrivateKey, ) @staticmethod def generate_private_key(crv: LiteralCurves) -> PrivateOKPKey: if crv not in PRIVATE_KEYS_MAP: raise InvalidKeyCurveError(f"Invalid curve value: '{crv}'") crv_key: t.Type[PrivateOKPKey] = PRIVATE_KEYS_MAP[crv] return crv_key.generate() @staticmethod def from_private_bytes(crv: LiteralCurves, data: bytes) -> PrivateOKPKey: crv_key: t.Type[PrivateOKPKey] = PRIVATE_KEYS_MAP[crv] return crv_key.from_private_bytes(data) @staticmethod def from_public_bytes(crv: LiteralCurves, data: bytes) -> PublicOKPKey: crv_key: t.Type[PublicOKPKey] = PUBLIC_KEYS_MAP[crv] return crv_key.from_public_bytes(data) @classmethod def import_private_key(cls, obj: OKPDictKey) -> PrivateOKPKey: d = urlsafe_b64decode(to_bytes(obj["d"])) return cls.from_private_bytes(obj["crv"], d) @classmethod def import_public_key(cls, obj: OKPDictKey) -> PublicOKPKey: x = urlsafe_b64decode(to_bytes(obj["x"])) return cls.from_public_bytes(obj["crv"], x) @classmethod def export_private_key(cls, key: PrivateOKPKey) -> dict[str, str]: obj = cls.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) -> 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 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] # this if else logic is used for type hints 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) -> LiteralCurves: return get_key_curve(self.raw_value) @classmethod def import_key( cls: t.Any, value: AnyKey | PrivateOKPKey | PublicOKPKey, parameters: KeyParameters | None = None, password: t.Any = None, ) -> "OKPKey": key: OKPKey if isinstance( value, ( Ed25519PrivateKey, Ed448PrivateKey, X25519PrivateKey, X448PrivateKey, Ed25519PublicKey, Ed448PublicKey, X25519PublicKey, X448PublicKey, ), ): key = cls(value, value, parameters) else: key = super(OKPKey, cls).import_key(value, parameters, password) return key @classmethod def generate_key( cls: t.Type["OKPKey"], crv: LiteralCurves | 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: raw_key = cls.binding.generate_private_key("Ed25519") else: raw_key = cls.binding.generate_private_key(crv) return _wrap_key(cls, raw_key, private, auto_kid, parameters) @classmethod def derive_key( cls: t.Type["OKPKey"], secret: bytes | str, crv: LiteralCurves = "Ed25519", parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, kdf_name: t.Literal["HKDF", "PBKDF2"] = "HKDF", kdf_options: dict[str, t.Any] | None = None, ) -> "OKPKey": """ Derives a key from a given input secret using a specified key derivation function (KDF) and elliptic curve algorithm. To derive a key using **HKDF**, the ``kdf_options`` may contain the ``algorithm``, ``salt`` and ``info`` values: .. code-block:: python from cryptography.hazmat.primitives import hashes from joserfc.jwk import OKPKey # default kdf_name is HKDF, algorithm is SHA256 OKPKey.derive_key("secret") # equivalent to OKPKey.derive_key( "secret", "Ed25519", kdf_name="HKDF", kdf_options={ "algorithm": hashes.SHA256(), "salt": b"joserfc:OKP:HKDF:Ed25519", "info": b"", } ) To derive a key using **PBKDF2**, the ``kdf_options`` may contain the ``algorithm``, ``salt`` and ``iterations`` values: .. code-block:: python from cryptography.hazmat.primitives import hashes from joserfc.jwk import OKPKey OKPKey.derive_key("secret", kdf_name="PBKDF2") # equivalent to OKPKey.derive_key( "secret", "Ed25519", kdf_name="PBKDF2", kdf_options={ "algorithm": hashes.SHA256(), "salt": b"joserfc:OKP:PBKDF2:Ed25519", "iterations": 100000, } ) :param secret: The input secret used for key derivation :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 :param kdf_name: Key derivation function name :param kdf_options: Additional options for the KDF """ if kdf_options is None: kdf_options = {} algorithm = kdf_options.pop("algorithm", None) if algorithm is None: algorithm = hashes.SHA256() kdf_options.setdefault("salt", to_bytes(f"joserfc:OKP:{kdf_name}:{crv}")) if kdf_name == "HKDF": kdf_options.setdefault("info", b"") hkdf = HKDF( algorithm=algorithm, length=OKP_SEED_SIZES[crv], **kdf_options, ) seed = hkdf.derive(to_bytes(secret)) elif kdf_name == "PBKDF2": kdf_options.setdefault("iterations", 100000) pbkdf2 = PBKDF2HMAC( algorithm=algorithm, length=OKP_SEED_SIZES[crv], **kdf_options, ) seed = pbkdf2.derive(to_bytes(secret)) else: raise ValueError(f"Invalid kdf value: '{kdf_name}'") raw_key = cls.binding.from_private_bytes(crv, seed) return _wrap_key(cls, raw_key, private, auto_kid, parameters) def get_key_curve(key: t.Union[PublicOKPKey, PrivateOKPKey]) -> LiteralCurves: 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 def _wrap_key( cls: t.Type["OKPKey"], raw_key: PrivateOKPKey, private: bool, auto_kid: bool, parameters: KeyParameters | None = None, ) -> OKPKey: 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 authlib-joserfc-aae7743/src/joserfc/_rfc8812/000077500000000000000000000000001511744432500206745ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc8812/__init__.py000066400000000000000000000006051511744432500230060ustar00rootroot00000000000000from cryptography.hazmat.primitives.asymmetric.ec import SECP256K1 from .._rfc7518.ec_key import ECKey from .._rfc7518.jws_algs import ESAlgorithm ES256K = ESAlgorithm("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"] authlib-joserfc-aae7743/src/joserfc/_rfc9278/000077500000000000000000000000001511744432500207035ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc9278/__init__.py000066400000000000000000000017161511744432500230210ustar00rootroot00000000000000import typing as t from .._rfc7638 import calculate_thumbprint JWK_THUMBPRINT_URN = "urn:ietf:params:oauth:jwk-thumbprint" def calculate_thumbprint_uri( value: dict[str, t.Any], digest_method: t.Literal["sha256", "sha384", "sha512"] = "sha256", ) -> str: """Calculate JWK thumbprint URI, defined by RFC9278. .. code-block:: python from joserfc import jwk jwk.thumbprint({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' """ thumbprint = calculate_thumbprint(value, digest_method=digest_method) return concat_thumbprint_uri(thumbprint, digest_method=digest_method) def concat_thumbprint_uri(value: str, digest_method: t.Literal["sha256", "sha384", "sha512"]) -> str: method = digest_method.replace("sha", "sha-") return f"{JWK_THUMBPRINT_URN}:{method}:{value}" authlib-joserfc-aae7743/src/joserfc/_rfc9864/000077500000000000000000000000001511744432500207045ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/_rfc9864/__init__.py000066400000000000000000000001701511744432500230130ustar00rootroot00000000000000from .jws_eddsa import JWS_ALGORITHMS, Ed25519, Ed448 __all__ = [ "JWS_ALGORITHMS", "Ed25519", "Ed448", ] authlib-joserfc-aae7743/src/joserfc/_rfc9864/jws_eddsa.py000066400000000000000000000026531511744432500232270ustar00rootroot00000000000000import typing as t from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey, Ed25519PrivateKey from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PublicKey, Ed448PrivateKey from ..errors import InvalidKeyCurveError from .._rfc7515.model import JWSAlgModel from .._rfc8037.okp_key import OKPKey class EdDSAAlgorithm(JWSAlgModel): key_type = "OKP" def __init__(self, curve: t.Literal["Ed25519", "Ed448"]): self.name = curve self.curve = curve self.description = f"EdDSA using the {curve} parameter set" def check_key(self, key: OKPKey) -> None: super().check_key(key) if key.curve_name != self.curve: raise InvalidKeyCurveError(f"Key for '{self.name}' not supported, only '{self.curve}' allowed") def sign(self, msg: bytes, key: OKPKey) -> bytes: op_key = t.cast(t.Union[Ed25519PrivateKey, Ed448PrivateKey], key.get_op_key("sign")) return op_key.sign(msg) def verify(self, msg: bytes, sig: bytes, key: OKPKey) -> bool: op_key = t.cast(t.Union[Ed25519PublicKey, Ed448PublicKey], key.get_op_key("verify")) try: op_key.verify(sig, msg) return True except InvalidSignature: return False Ed25519 = EdDSAAlgorithm("Ed25519") Ed448 = EdDSAAlgorithm("Ed448") JWS_ALGORITHMS: list[EdDSAAlgorithm] = [Ed25519, Ed448] authlib-joserfc-aae7743/src/joserfc/drafts/000077500000000000000000000000001511744432500207235ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/drafts/__init__.py000066400000000000000000000000001511744432500230220ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/drafts/jwe_chacha20.py000066400000000000000000000027031511744432500235150ustar00rootroot00000000000000from __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_chacha20_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_chacha20_poly1305() -> None: for model in JWE_ENC_MODELS: JWERegistry.register(model) authlib-joserfc-aae7743/src/joserfc/drafts/jwe_ecdh_1pu.py000066400000000000000000000117341511744432500236400ustar00rootroot00000000000000from __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 authlib-joserfc-aae7743/src/joserfc/errors.py000066400000000000000000000144561511744432500213400ustar00rootroot00000000000000from __future__ import annotations class SecurityWarning(UserWarning): """Base class for warnings of security issues.""" pass 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) # --- Key related errors --- # class KeyParameterError(JoseError): error = "key_parameter" class MissingKeyError(JoseError): error = "missing_key" class UnsupportedKeyUseError(KeyParameterError): error = "unsupported_key_use" class UnsupportedKeyAlgorithmError(KeyParameterError): error = "unsupported_key_alg" class UnsupportedKeyOperationError(KeyParameterError): error = "unsupported_key_operation" class MissingKeyTypeError(KeyParameterError): error = "missing_key_type" class InvalidKeyTypeError(KeyParameterError): error = "invalid_key_type" class InvalidKeyIdError(JoseError): """This error is designed for Key Set. It is raised when a key can not be found with the given key ID.""" error = "invalid_key_id" class InvalidExchangeKeyError(JoseError): """This error is designed for EC and OKP keys. It is raised when exchanging derive key failed.""" error = "invalid_exchange_key" description = "Invalid key for exchanging shared key" # --- JWS & JWE related errors --- # class DecodeError(JoseError): """This error is designed for both JWS and JWE. It is raised when deserialization and decryption fails. """ error = "decode_error" class MissingAlgorithmError(JoseError): """Raised when an algorithm ("alg") is missing.""" error = "missing_algorithm" description = "Missing 'alg' value in header" class ConflictAlgorithmError(JoseError): error = "conflict_algorithm" class UnsupportedAlgorithmError(JoseError): """This error is designed for both JWS and JWE. It is raised when the given algorithm is not supported in the registry. """ error = "unsupported_algorithm" class InvalidHeaderValueError(JoseError): """Raised when the given header's value is invalid.""" error = "invalid_header_value" class UnsupportedHeaderError(JoseError): """Raised when an unsupported header is encountered.""" 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 InvalidKeyCurveError(JoseError): """This error is designed for JWS. It is raised when key's curve name does not match with the given algorithm. """ error = "invalid_key_curve" class InvalidKeyLengthError(JoseError): """This error is designed for JWE. It is raised when key's length does not align with the given algorithm. """ error = "invalid_key_length" class BadSignatureError(JoseError): """This error is designed for JWS. It is raised when signature does not match. """ error = "bad_signature" class ExceededSizeError(JoseError): """This error is designed for validating the token's content size. It raised when the 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 InvalidEncryptedKeyError(JoseError): error = "invalid_encrypted_key" description = "JWE Encrypted Key value SHOULD be an empty octet sequence" class InvalidCEKLengthError(JoseError): error = "invalid_cek_length" description = "Invalid 'cek' length" def __init__(self, cek_size: int): # pragma: no cover description = f"A key of size {cek_size} bits MUST be used" super(InvalidCEKLengthError, self).__init__(description=description) # --- JWT related errors --- # class ClaimError(JoseError): """This a base error for JWT claims validation.""" claim: str description = "Error claim: '{}'" def __init__(self, claim: str): self.claim = claim description = self.description.format(claim) super(ClaimError, self).__init__(description=description) class InvalidClaimError(ClaimError): """This error is designed for JWT. It raised when the claim contains invalid values or types.""" error = "invalid_claim" description = "Invalid claim: '{}'" class MissingClaimError(ClaimError): """This error is designed for JWT. It raised when the required claims are missing.""" error = "missing_claim" description = "Missing claim: '{}'" class InsecureClaimError(ClaimError): """This error is designed for JWT. It raised when the claim contains sensitive information.""" error = "insecure_claim" description = "Insecure claim: '{}'" 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" authlib-joserfc-aae7743/src/joserfc/jwa.py000066400000000000000000000043571511744432500206040ustar00rootroot00000000000000from ._rfc7515.registry import JWSRegistry from ._rfc7515.model import JWSAlgModel from ._rfc7516.registry import JWERegistry from ._rfc7516.models import ( JWEDirectEncryption, JWEKeyEncryption, JWEKeyWrapping, JWEKeyAgreement, JWEAlgModel, JWEEncModel, JWEZipModel, ) from ._rfc7518.jws_algs import ( NoneAlgorithm, HMACAlgorithm, RSAAlgorithm, ESAlgorithm, RSAPSSAlgorithm, JWS_ALGORITHMS as RFC7518_JWS_ALGORITHMS, ) from ._rfc7518.jwe_algs import ( DirectAlgEncryption, AESAlgKeyWrapping, ECDHESAlgKeyAgreement, AESGCMAlgKeyWrapping, PBES2HSAlgKeyEncryption, JWE_ALG_MODELS, ) from ._rfc7518.jwe_encs import ( CBCHS2EncModel, GCMEncModel, JWE_ENC_MODELS, ) from ._rfc7518.jwe_zips import ( DeflateZipModel, JWE_ZIP_MODELS, ) from ._rfc8037.jws_eddsa import EdDSA, EdDSAAlgorithm from ._rfc8812 import ES256K from ._rfc9864 import JWS_ALGORITHMS as RFC9864_JWS_ALGORITHMS from ._keys import KeySet __all__ = [ # JWS algorithms "JWS_ALGORITHMS", "JWSAlgModel", "NoneAlgorithm", "HMACAlgorithm", "RSAAlgorithm", "ESAlgorithm", "RSAPSSAlgorithm", "EdDSAAlgorithm", # JWE algorithms "JWE_ALG_MODELS", "JWE_ENC_MODELS", "JWE_ZIP_MODELS", "JWEAlgModel", "JWEDirectEncryption", "JWEKeyEncryption", "JWEKeyWrapping", "JWEKeyAgreement", "DirectAlgEncryption", "AESAlgKeyWrapping", "ECDHESAlgKeyAgreement", "AESGCMAlgKeyWrapping", "PBES2HSAlgKeyEncryption", "JWEEncModel", "CBCHS2EncModel", "GCMEncModel", "JWEZipModel", "DeflateZipModel", # setup methods "setup_jws_algorithms", "setup_jwe_algorithms", ] JWS_ALGORITHMS = [ *RFC7518_JWS_ALGORITHMS, EdDSA, ES256K, *RFC9864_JWS_ALGORITHMS, ] def setup_jws_algorithms() -> None: for _alg in JWS_ALGORITHMS: JWSRegistry.register(_alg) KeySet.algorithm_keys[_alg.name] = [_alg.key_type] def setup_jwe_algorithms() -> None: for _alg in JWE_ALG_MODELS: KeySet.algorithm_keys[_alg.name] = _alg.key_types JWERegistry.register(_alg) for _enc in JWE_ENC_MODELS: JWERegistry.register(_enc) for _zip in JWE_ZIP_MODELS: JWERegistry.register(_zip) authlib-joserfc-aae7743/src/joserfc/jwe.py000066400000000000000000000233501511744432500206020ustar00rootroot00000000000000from __future__ import annotations from typing import overload from ._rfc7516.types import ( GeneralJSONSerialization, FlattenedJSONSerialization, ) from ._rfc7516.models import ( Recipient, CompactEncryption, GeneralJSONEncryption, FlattenedJSONEncryption, ) from ._rfc7516.registry import ( 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 .jwa import setup_jwe_algorithms from .jwk import Key, KeySet, ECKey, OKPKey, KeyFlexible, guess_key from .util import to_bytes from .registry import Header, reject_unprotected_crit_header __all__ = [ # types "GeneralJSONSerialization", "FlattenedJSONSerialization", # modules "JWERegistry", "Recipient", "CompactEncryption", "GeneralJSONEncryption", "FlattenedJSONEncryption", # methods "encrypt_compact", "decrypt_compact", "encrypt_json", "decrypt_json", # consts "default_registry", ] setup_jwe_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, use="enc") 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`` """ if algorithms: registry = JWERegistry(algorithms=algorithms) elif registry is None: registry = default_registry obj = extract_compact(to_bytes(value), registry) recipient = obj.recipient assert recipient is not None key = guess_key(private_key, recipient, use="enc") 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 reject_unprotected_crit_header(obj.unprotected) 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, use="enc") 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 reject_unprotected_crit_header(data.get("unprotected")) if "recipients" in data: general_obj = extract_general_json(data, registry) # 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, registry) # 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, use="enc") 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 authlib-joserfc-aae7743/src/joserfc/jwk.py000066400000000000000000000145061511744432500206130ustar00rootroot00000000000000from __future__ import annotations import typing as t import warnings from ._keys import ( JWKRegistry, KeySet, Key, KeySetSerialization, ) from ._rfc7517.pem import import_from_pem_key, import_from_ssh_key from ._rfc7517.types import AnyKey, DictKey, KeyParameters 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 ._rfc8812 import register_secp256k1 from ._rfc7638 import calculate_thumbprint as thumbprint from ._rfc9278 import calculate_thumbprint_uri as thumbprint_uri from .errors import SecurityWarning, InvalidKeyTypeError from .registry import Header from .util import to_bytes __all__ = [ # types "Key", "DictKey", "KeyParameters", "KeyCallable", "KeyFlexible", "KeySetSerialization", "KeyBase", "GuestProtocol", # modules "JWKRegistry", "OctKey", "RSAKey", "ECKey", "OKPKey", "KeySet", # methods "guess_key", "import_key", "generate_key", "thumbprint", "thumbprint_uri", ] register_secp256k1() class GuestProtocol(t.Protocol): # pragma: no cover def headers(self) -> Header: ... def set_kid(self, kid: str) -> None: ... KeyBase = t.Union[Key, KeySet] KeyCallable = t.Callable[[GuestProtocol], KeyBase] KeyFlexible = t.Union[KeyBase, KeyCallable] def guess_key( key: KeyFlexible, obj: GuestProtocol, random: bool = False, use: t.Literal["sig", "enc"] | None = None, ) -> 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 random: pick a random key from key set :param use: optional "use" value """ resolved_key: KeyBase if callable(key): resolved_key = key(obj) else: resolved_key = key if isinstance(resolved_key, (OctKey, RSAKey, ECKey, OKPKey)): return resolved_key elif isinstance(resolved_key, KeySet): headers = obj.headers() kid: str | None = headers.get("kid") parameters: KeyParameters = {"alg": headers["alg"]} if use: parameters["use"] = use if not kid and random: # choose one key by random return_key = resolved_key.pick_random_key(headers["alg"], parameters) if return_key is None: raise ValueError("Invalid key") return_key.ensure_kid() obj.set_kid(t.cast(str, return_key.kid)) else: return_key = resolved_key.get_by_kid(kid, parameters) return return_key else: raise ValueError("Invalid key") @t.overload def import_key(data: AnyKey, key_type: t.Literal["oct"], parameters: KeyParameters | None = None) -> OctKey: ... @t.overload def import_key(data: AnyKey, key_type: t.Literal["RSA"], parameters: KeyParameters | None = None) -> RSAKey: ... @t.overload def import_key(data: AnyKey, key_type: t.Literal["EC"], parameters: KeyParameters | None = None) -> ECKey: ... @t.overload def import_key(data: AnyKey, key_type: t.Literal["OKP"], parameters: KeyParameters | None = None) -> OKPKey: ... @t.overload def import_key(data: AnyKey, key_type: None = None, parameters: KeyParameters | None = None) -> Key: ... def import_key( data: AnyKey, key_type: t.Literal["oct", "RSA", "EC", "OKP"] | 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 """ if isinstance(data, (str, bytes)) and key_type is None: warnings.warn("Using implicit key type is not recommended.", SecurityWarning) value = to_bytes(data) ssh_types = tuple( cls.binding.ssh_type for cls in JWKRegistry.key_types.values() if hasattr(cls.binding, "ssh_type") ) if value.startswith(ssh_types): try: raw_key = import_from_ssh_key(value) except ValueError: return OctKey.import_key(value, parameters) else: try: raw_key = import_from_pem_key(value) except ValueError: return OctKey.import_key(value, parameters) for cls in JWKRegistry.key_types.values(): if hasattr(cls.binding, "check_cryptography_key") and cls.binding.check_cryptography_key(raw_key): return cls(raw_key, data, parameters) raise InvalidKeyTypeError("Not a key of any supported type") # pragma: no cover return JWKRegistry.import_key(data, key_type, parameters) @t.overload def generate_key( key_type: t.Literal["oct"], crv_or_size: int | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> OctKey: ... @t.overload def generate_key( key_type: t.Literal["RSA"], crv_or_size: int | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> RSAKey: ... @t.overload def generate_key( key_type: t.Literal["EC"], crv_or_size: t.Literal["P-256", "P-384", "P-521", "secp256k1"] | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> ECKey: ... @t.overload def generate_key( key_type: t.Literal["OKP"], crv_or_size: t.Literal["Ed25519", "Ed448", "X25519", "X448"] | None = None, parameters: KeyParameters | None = None, private: bool = True, auto_kid: bool = False, ) -> OKPKey: ... def generate_key( key_type: t.Literal["oct", "RSA", "EC", "OKP"], 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) authlib-joserfc-aae7743/src/joserfc/jws.py000066400000000000000000000247541511744432500206310ustar00rootroot00000000000000from __future__ import annotations from typing import overload, TypeVar, Any from ._rfc7515.model import ( JWSAlgModel, HeaderMember, CompactSignature, GeneralJSONSignature, FlattenedJSONSignature, ) from ._rfc7515.registry import ( JWSRegistry, construct_registry, default_registry, ) from ._rfc7515.compact import ( 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, detach_json_content, ) from ._rfc7515.types import ( HeaderDict, GeneralJSONSerialization, FlattenedJSONSerialization, ) from ._rfc7797.util import is_rfc7797_enabled from ._rfc7797.compact import ( sign_rfc7515_compact, extract_rfc7515_compact as extract_compact, ) from ._rfc7797.json import ( sign_rfc7797_json, extract_rfc7797_json as extract_flattened_json, ) from .errors import BadSignatureError, MissingKeyError from .jwk import Key, KeyFlexible, guess_key from .jwa import setup_jws_algorithms from .util import to_bytes from .registry import Header __all__ = [ # types "HeaderDict", "GeneralJSONSerialization", "FlattenedJSONSerialization", # modules "JWSRegistry", "HeaderMember", "CompactSignature", "GeneralJSONSignature", "FlattenedJSONSignature", # methods "serialize_compact", "deserialize_compact", "extract_compact", "validate_compact", "serialize_json", "deserialize_json", "detach_content", # consts "default_registry", ] setup_jws_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) is_rfc7797 = is_rfc7797_enabled(protected) obj = CompactSignature(protected, to_bytes(payload)) alg: JWSAlgModel = registry.get_alg(protected["alg"]) # "none" algorithm requires no key key: Key | None = None if alg.name != "none": if private_key is None: raise MissingKeyError() key = guess_key(private_key, obj, True, use="sig") alg.check_key(key) if is_rfc7797: out = sign_rfc7515_compact(obj, alg, key) else: 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, use="sig") alg.check_key(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, payload: bytes | str | 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 :param payload: optional payload, required with detached content :raises BadSignatureError: when signature verification fails :return: object of the CompactSignature """ obj = extract_compact(to_bytes(value), payload, registry) 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: HeaderMember) -> Key: return guess_key(private_key, obj, True, use="sig") _payload = to_bytes(payload) if isinstance(members, list): _members = [HeaderMember(**member) for member in members] return sign_general_json(_members, _payload, registry, find_key) else: member = HeaderMember(**members) if is_rfc7797_enabled(member.headers()): return sign_rfc7797_json(member, _payload, registry, find_key) return sign_flattened_json(member, _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 GeneralJSONSignature or FlattenedJSONSignature :raises BadSignatureError: when signature verification fails """ if registry is None: registry = construct_registry(algorithms) def find_key(obj: HeaderMember) -> Key: return guess_key(public_key, obj, use="sig") if "signatures" in value: general_obj = extract_general_json(value, registry) if not verify_general_json(general_obj, registry, find_key): raise BadSignatureError() return general_obj else: flattened_obj = extract_flattened_json(value, registry) 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) authlib-joserfc-aae7743/src/joserfc/jwt.py000066400000000000000000000077341511744432500206310ustar00rootroot00000000000000from __future__ import annotations import json from json import JSONEncoder, JSONDecoder from typing import Type from ._rfc7519.claims import ( convert_claims, Claims, ClaimsOption, BaseClaimsRegistry, JWTClaimsRegistry, ) from ._rfc7519.security import check_sensitive_data 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__ = [ # types "Claims", "ClaimsOption", # modules "BaseClaimsRegistry", "JWTClaimsRegistry", "Token", # methods "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, default_type: str | None = "JWT", ) -> 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 :param default_type: default value of the ``typ`` header parameter """ if default_type is not None: _header = {"typ": default_type, **header} else: _header = {**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: when signature verification fails :raise InvalidPayloadError: when payload is not a valid JSON object """ _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 authlib-joserfc-aae7743/src/joserfc/py.typed000066400000000000000000000000001511744432500211250ustar00rootroot00000000000000authlib-joserfc-aae7743/src/joserfc/registry.py000066400000000000000000000165651511744432500216770ustar00rootroot00000000000000from __future__ import annotations from typing import Any, Callable, Union from .errors import ( MissingHeaderError, MissingCritHeaderError, UnsupportedHeaderError, InvalidHeaderValueError, ) Header = dict[str, Any] def is_str(value: Any) -> 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), # Enable RFC7797 by default. "b64": HeaderParameter("JWS Signing Input Formula", is_bool), } #: 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 check_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(registry: HeaderRegistryDict, header: Header) -> None: # check `crit` header missing_crit_headers = [] unsupported_crit_headers = [] if "crit" in header: for k in header["crit"]: if k not in header: missing_crit_headers.append(k) elif k not in registry: unsupported_crit_headers.append(k) if missing_crit_headers: raise MissingCritHeaderError(",".join(missing_crit_headers)) elif unsupported_crit_headers: raise UnsupportedHeaderError(f"Unsupported {unsupported_crit_headers} in header") def reject_unprotected_crit_header(unprotected: Header | None) -> None: if unprotected and "crit" in unprotected: raise UnsupportedHeaderError("'crit' header MUST be protected header") authlib-joserfc-aae7743/src/joserfc/util.py000066400000000000000000000032601511744432500207700ustar00rootroot00000000000000from __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(data: dict[str, Any]) -> bytes: text = json.dumps(data, ensure_ascii=True, separators=(",", ":")) return urlsafe_b64encode(to_bytes(text, "ascii")) def json_b64decode(text: bytes) -> Any: return json.loads(urlsafe_b64decode(text)) authlib-joserfc-aae7743/tests/000077500000000000000000000000001511744432500163605ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/__init__.py000066400000000000000000000000001511744432500204570ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/base.py000066400000000000000000000026521511744432500176510ustar00rootroot00000000000000import 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() authlib-joserfc-aae7743/tests/fixtures/000077500000000000000000000000001511744432500202315ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/fixtures/jwe_compact_ecdh_1pu.json000066400000000000000000000100721511744432500251670ustar00rootroot00000000000000{ "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" } ] } authlib-joserfc-aae7743/tests/fixtures/jwe_compact_ecdh_es.json000066400000000000000000000000001511744432500250570ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/fixtures/jwe_rfc7520.json000066400000000000000000000337611511744432500230730ustar00rootroot00000000000000{ "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" } } ] } authlib-joserfc-aae7743/tests/fixtures/jws_examples.json000066400000000000000000000264611511744432500236360ustar00rootroot00000000000000{ "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" } } ] } authlib-joserfc-aae7743/tests/fixtures/jws_rfc7520.json000066400000000000000000000207361511744432500231070ustar00rootroot00000000000000{ "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" } } ] } authlib-joserfc-aae7743/tests/fixtures/jws_rfc7797.json000066400000000000000000000036511511744432500231240ustar00rootroot00000000000000{ "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" } } ] } authlib-joserfc-aae7743/tests/fixtures/jwt_use_jws.json000066400000000000000000000070071511744432500234730ustar00rootroot00000000000000{ "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" } ] } authlib-joserfc-aae7743/tests/jwe/000077500000000000000000000000001511744432500171455ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwe/__init__.py000066400000000000000000000000001511744432500212440ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwe/test_chacha20.py000066400000000000000000000046071511744432500221360ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import ( encrypt_compact, decrypt_compact, JWERegistry, ) from joserfc.jwk import OctKey from joserfc.drafts.jwe_chacha20 import register_chacha20_poly1305 register_chacha20_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) authlib-joserfc-aae7743/tests/jwe/test_compact.py000066400000000000000000000251451511744432500222130ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import ( JWERegistry, encrypt_compact, decrypt_compact, CompactEncryption, ) from joserfc.jwa import JWE_ENC_MODELS from joserfc.jwk import RSAKey, ECKey, OctKey, OKPKey, KeySet from joserfc.errors import ( InvalidKeyLengthError, MissingAlgorithmError, MissingEncryptionError, DecodeError, ExceededSizeError, InvalidHeaderValueError, ) from joserfc.util import json_b64encode, urlsafe_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_header_exceeds_size(self): header = json_b64encode({f"a{i}": "a" * i for i in range(1000)}).decode("utf-8") s = header + "..YbDfdYa6p-wAEFul.YK7j0MsH-Dko6ifsEg.wES6-QAOEbErZqXiS0JHRw" self.assertRaises(ExceededSizeError, decrypt_compact, s, OctKey.import_key("secret")) def test_encrypted_key_exceeds_size(self): header = json_b64encode({"alg": "dir", "enc": "A128GCM"}).decode("utf-8") ek = urlsafe_b64encode(("a" * 1000).encode("utf-8")).decode("utf-8") s = header + "." + ek + ".YbDfdYa6p-wAEFul.YK7j0MsH-Dko6ifsEg.wES6-QAOEbErZqXiS0JHRw" key = OctKey.import_key({"k": "pyL42ncDFSYnenl-GiZjRw", "kty": "oct"}) self.assertRaises(ExceededSizeError, decrypt_compact, s, key) def test_initialization_vector_size(self): header = json_b64encode({"alg": "dir", "enc": "A128GCM"}).decode("utf-8") iv = urlsafe_b64encode(("a" * 1000).encode("utf-8")).decode("utf-8") s = header + ".." + iv + ".YK7j0MsH-Dko6ifsEg.wES6-QAOEbErZqXiS0JHRw" key = OctKey.import_key({"k": "pyL42ncDFSYnenl-GiZjRw", "kty": "oct"}) self.assertRaises(ExceededSizeError, decrypt_compact, s, key) def test_ciphertext_exceeds_size(self): header = json_b64encode({"alg": "dir", "enc": "A128GCM"}).decode("utf-8") ciphertext = urlsafe_b64encode(("a" * 70000).encode("utf-8")).decode("utf-8") s = header + "..YbDfdYa6p-wAEFul." + ciphertext + ".wES6-QAOEbErZqXiS0JHRw" self.assertRaises(ExceededSizeError, decrypt_compact, s, OctKey.import_key("secret")) def test_auth_tag_exceeds_size(self): header = json_b64encode({"alg": "dir", "enc": "A128GCM"}).decode("utf-8") tag = urlsafe_b64encode(("a" * 80).encode("utf-8")).decode("utf-8") s = header + "..YbDfdYa6p-wAEFul.YK7j0MsH-Dko6ifsEg." + tag self.assertRaises(ExceededSizeError, decrypt_compact, s, OctKey.import_key("secret")) 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") authlib-joserfc-aae7743/tests/jwe/test_ecdh_1pu.py000066400000000000000000000217651511744432500222610ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwe import ( JWERegistry, GeneralJSONEncryption, GeneralJSONSerialization, encrypt_compact, decrypt_compact, encrypt_json, decrypt_json, ) from joserfc.jwa import JWE_ENC_MODELS from joserfc.jwk import KeySet from joserfc.errors import InvalidEncryptionAlgorithmError 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: GeneralJSONSerialization = { "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") authlib-joserfc-aae7743/tests/jwe/test_errors.py000066400000000000000000000077541511744432500221070ustar00rootroot00000000000000from 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) authlib-joserfc-aae7743/tests/jwe/test_example.py000066400000000000000000000654541511744432500222270ustar00rootroot00000000000000from 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.jwe import JWERegistry, default_registry as registry from joserfc.jwe 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) authlib-joserfc-aae7743/tests/jwe/test_json.py000066400000000000000000000053371511744432500215370ustar00rootroot00000000000000from 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, ) authlib-joserfc-aae7743/tests/jwk/000077500000000000000000000000001511744432500171535ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwk/__init__.py000066400000000000000000000000001511744432500212520ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwk/test_ec_key.py000066400000000000000000000114301511744432500220220ustar00rootroot00000000000000from unittest import TestCase from cryptography.hazmat.primitives import hashes from joserfc.jwk import ECKey, OctKey from joserfc.errors import ( InvalidExchangeKeyError, InvalidKeyTypeError, InvalidKeyCurveError, ) 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_import_from_native_keys(self): key = ECKey.generate_key() self.assertEqual(key, ECKey.import_key(key.private_key)) def test_generate_key(self): self.assertRaises(InvalidKeyCurveError, 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_import_invalid_pem_key(self): private_pem = read_key("rsa-openssl-private.pem") self.assertRaises(InvalidKeyTypeError, ECKey.import_key, private_pem) 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()) def test_derive_key_errors(self): self.assertRaises(InvalidKeyCurveError, ECKey.derive_key, "secret", "invalid") self.assertRaises(ValueError, ECKey.derive_key, "secret", kdf_name="invalid") def test_derive_key_with_default_kwargs(self): key1 = ECKey.derive_key("ec-secret-key") key2 = ECKey.derive_key("ec-secret-key") self.assertEqual(key1, key2) for crv in ["P-256", "P-384", "P-521", "secp256k1"]: key1 = ECKey.derive_key("ec-secret-key", crv) key2 = ECKey.derive_key("ec-secret-key", crv) self.assertEqual(key1, key2) for crv in ["P-256", "P-384", "P-521", "secp256k1"]: key1 = ECKey.derive_key("ec-secret-key", crv, kdf_name="PBKDF2") key2 = ECKey.derive_key("ec-secret-key", crv, kdf_name="PBKDF2") self.assertEqual(key1, key2) def test_derive_key_with_new_salt(self): curves = ["P-256", "P-384", "P-521", "secp256k1"] for crv in curves: key1 = ECKey.derive_key("ec-secret-key", crv, kdf_options={"salt": b"salt"}) key2 = ECKey.derive_key("ec-secret-key", crv, kdf_options={"salt": b"salt"}) self.assertEqual(key1, key2) def test_derive_key_with_different_hash(self): key1 = ECKey.derive_key("ec-secret-key", "P-256", kdf_options={"algorithm": hashes.SHA256()}) key2 = ECKey.derive_key("ec-secret-key", "P-256", kdf_options={"algorithm": hashes.SHA512()}) self.assertNotEqual(key1, key2) authlib-joserfc-aae7743/tests/jwk/test_jwk_set.py000066400000000000000000000064011511744432500222330ustar00rootroot00000000000000from 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) key3 = key_set.pick_random_key() self.assertIsNotNone(key3) 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) authlib-joserfc-aae7743/tests/jwk/test_key_methods.py000066400000000000000000000150221511744432500230770ustar00rootroot00000000000000from unittest import TestCase from joserfc import jws from joserfc.jwk import ( guess_key, import_key, generate_key, thumbprint_uri, ) from joserfc.jwk import KeySet, OctKey, RSAKey, ECKey, OKPKey from joserfc.errors import ( UnsupportedKeyAlgorithmError, UnsupportedKeyUseError, UnsupportedKeyOperationError, InvalidKeyTypeError, MissingKeyTypeError, InvalidKeyIdError, ) from tests.keys import read_key class Guest: def __init__(self): self._headers = {"alg": "HS256"} def headers(self): return self._headers def set_kid(self, kid): self._headers["kid"] = kid class TestKeyMethods(TestCase): def test_guess_callable_key(self): oct_key = OctKey.generate_key(parameters={"kid": "1"}) rsa_key = RSAKey.generate_key(parameters={"kid": "2"}) def rsa_key_func(obj): return rsa_key def key_set_func(obj): return KeySet([oct_key, rsa_key]) key = guess_key(rsa_key_func, Guest()) self.assertIsInstance(key, RSAKey) guest = Guest() guest.set_kid("2") key = guess_key(key_set_func, guest) self.assertIsInstance(key, 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, {}) def test_thumbprint_uri(self): value = thumbprint_uri( { "kty": "EC", "crv": "P-256", "x": "jJ6Flys3zK9jUhnOHf6G49Dyp5hah6CNP84-gY-n9eo", "y": "nhI6iD5eFXgBTLt_1p3aip-5VbZeMhxeFSpjfEAf7Ww", } ) expected = "urn:ietf:params:oauth:jwk-thumbprint:sha-256:w9eYdC6_s_tLQ8lH6PUpc0mddazaqtPgeC2IgWDiqY8" self.assertEqual(value, expected) def test_find_correct_key_with_use(self): key = OctKey.generate_key() dict_key = key.as_dict() key1 = OctKey.import_key(dict_key, {"use": "enc"}) key2 = OctKey.import_key(dict_key, {"use": "sig"}) self.assertEqual(key1.kid, key2.kid) key_set = KeySet([key1, key2]) # pick randomly jws.serialize_compact({"alg": "HS256"}, "foo", key_set) # get by kid jws.serialize_compact({"alg": "HS256", "kid": key2.kid}, "foo", key_set) key_set = KeySet([key1, key2, key2]) # return the first found key jws.serialize_compact({"alg": "HS256", "kid": key2.kid}, "foo", key_set) def test_find_correct_key_with_alg(self): key = OctKey.generate_key() dict_key = key.as_dict() key1 = OctKey.import_key(dict_key, {"alg": "HS256"}) key2 = OctKey.import_key(dict_key, {"alg": "dir"}) self.assertEqual(key1.kid, key2.kid) key_set = KeySet([key1, key2]) # pick randomly jws.serialize_compact({"alg": "HS256"}, "foo", key_set) # get by kid jws.serialize_compact({"alg": "HS256", "kid": key2.kid}, "foo", key_set) def test_import_bytes_keys(self): # check by ssh types key = import_key(read_key("ssh-rsa-public.pem")) self.assertEqual(key.key_type, "RSA") key = import_key(read_key("ssh-ecdsa-public.pem")) self.assertEqual(key.key_type, "EC") key = import_key(read_key("ssh-ed25519-public.pem")) self.assertEqual(key.key_type, "OKP") key = import_key("ssh-rsa-oct") self.assertEqual(key.key_type, "oct") # check by pem types key = import_key(read_key("rsa-openssl-public.pem")) self.assertEqual(key.key_type, "RSA") key = import_key(read_key("ec-p256-public.pem")) self.assertEqual(key.key_type, "EC") key = import_key(read_key("okp-ed448-public.pem")) self.assertEqual(key.key_type, "OKP") key = import_key(b"oct-key") self.assertEqual(key.key_type, "oct") authlib-joserfc-aae7743/tests/jwk/test_oct_key.py000066400000000000000000000075661511744432500222370ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import OctKey from joserfc.errors import SecurityWarning, KeyParameterError 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_thumbprint_uri(self): data = { "kty": "oct", "alg": "A128KW", "k": "GawgguFyGrWKav7AX4VKUg", "use": "sig", "key_ops": ["sign", "verify"], } key = OctKey.import_key(data) thumbprint = "k1JnWRfC-5zzmL72vXIuBgTLfVROXBakS4OmGcrMCoc" self.assertEqual(key.thumbprint_uri(), f"urn:ietf:params:oauth:jwk-thumbprint:sha-256:{thumbprint}") def test_import_missing_k(self): data = { "kty": "oct", "alg": "A128KW", } self.assertRaises(KeyParameterError, OctKey.import_key, data) def test_invalid_typeof_k(self): data = { "kty": "oct", "alg": "A128KW", "k": 123, } self.assertRaises(KeyParameterError, 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(KeyParameterError, OctKey.import_key, data) def test_invalid_use(self): data = { "kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg", "use": "invalid", } self.assertRaises(KeyParameterError, OctKey.import_key, data) def test_invalid_key_ops(self): data = { "kty": "oct", "k": "GawgguFyGrWKav7AX4VKUg", "key_ops": ["invalid"], } self.assertRaises(KeyParameterError, OctKey.import_key, data) def test_import_pem_key(self): public_pem = read_key("ec-p256-public.pem") self.assertWarns(SecurityWarning, 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_generate_key_with_warnings(self): self.assertWarns(SecurityWarning, OctKey.generate_key, 16) def test_import_key_with_warnings(self): self.assertWarns(SecurityWarning, OctKey.import_key, b"rfc") 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) authlib-joserfc-aae7743/tests/jwk/test_okp_key.py000066400000000000000000000136041511744432500222310ustar00rootroot00000000000000from unittest import TestCase from cryptography.hazmat.primitives import hashes from joserfc.jwk import OKPKey from joserfc.errors import ( InvalidExchangeKeyError, InvalidKeyTypeError, InvalidKeyCurveError, ) 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(InvalidKeyCurveError, 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_import_invalid_pem_key(self): public_pem = read_key("ec-p256-public.pem") self.assertRaises(InvalidKeyTypeError, OKPKey.import_key, public_pem) 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_import_from_native_keys(self): curves = ["Ed25519", "Ed448", "X25519", "X448"] for crv in curves: key = OKPKey.generate_key(crv) self.assertEqual(key, OKPKey.import_key(key.private_key)) 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) def test_derive_key_errors(self): self.assertRaises(KeyError, OKPKey.derive_key, "secret", "invalid") self.assertRaises(ValueError, OKPKey.derive_key, "secret", "Ed25519", kdf_name="invalid") def test_derive_key_with_default_kwargs(self): curves = ["Ed25519", "Ed448", "X25519", "X448"] for crv in curves: key1 = OKPKey.derive_key("okp-secret-key", crv) key2 = OKPKey.derive_key("okp-secret-key", crv) self.assertEqual(key1, key2) for crv in curves: key1 = OKPKey.derive_key("okp-secret-key", crv, kdf_name="PBKDF2") key2 = OKPKey.derive_key("okp-secret-key", crv, kdf_name="PBKDF2") self.assertEqual(key1, key2) def test_derive_key_with_new_salt(self): curves = ["Ed25519", "Ed448", "X25519", "X448"] for crv in curves: key1 = OKPKey.derive_key("okp-secret-key", crv, kdf_options={"salt": b"salt"}) key2 = OKPKey.derive_key("okp-secret-key", crv, kdf_options={"salt": b"salt"}) self.assertEqual(key1, key2) def test_derive_key_with_different_hash(self): key1 = OKPKey.derive_key("okp-secret-key", "Ed25519", kdf_options={"algorithm": hashes.SHA256()}) key2 = OKPKey.derive_key("okp-secret-key", "Ed25519", kdf_options={"algorithm": hashes.SHA512()}) self.assertNotEqual(key1, key2) authlib-joserfc-aae7743/tests/jwk/test_rsa_key.py000066400000000000000000000172501511744432500222260ustar00rootroot00000000000000from unittest import TestCase from joserfc.jwk import RSAKey from joserfc.errors import SecurityWarning, KeyParameterError, InvalidKeyTypeError 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(KeyParameterError, 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(KeyParameterError, 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_import_from_native_keys(self): key = RSAKey.generate_key() self.assertEqual(key, RSAKey.import_key(key.private_key)) 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_generate_key_with_warnings(self): self.assertWarns(SecurityWarning, RSAKey.generate_key, 1024) def test_import_key_with_warnings(self): rsa_jwk = { "n": ( "qEr7algLblN5qcstFCfOkAVERAWOyq3UuIor3BZq6s932Zs97yrkKw6XhKobGlNKEXNJhFiKU9oG-XA1dyvwv" "9uRbFPiUxLC0IS1mnIeF1Uz3n9h8o3v23TIkbcTPPNsJJuSPiRybefddDBtld7i_9mzNjDR4Ios6DJCNthnIKc" ), "e": "AQAB", "d": ( "QfOYkXVNjX_TFvJTiSmMbq5RsWKIMe9rhKJJS-fRIJILgtCutdKWNjVytX_APVHUngATGHVmSDQSNaB-o2Qp5TMG" "0KQ8-TuCXD3nRQIsDj0CCRSuq_CoVUjsFkP3hPvJ8MStN5xpyNGWusHNjgyz_KMQMziFM2wnjRIpMw7J3ck" ), "p": "3fJ5EmJpD7YCElVtAWBUWSVw5uO_NcvDMvTsshVsSY7H6atE4UZSsKEy3HKmVLB3zfqHZYg1Bd8DV52EnGOogw", "q": "wh0awdt7hnpdHJGbrbaC1Pr12MRd6bnraPTGewBB9o6KxPXpkSVBlm5mLuhEiB0gnuA933zxvt1bSHHUNuGGDQ", "dp": "1GFJ6YWx8w6_PLvx6vc6v3NMbiRQvDGXQBOOy3okfN7b_YWeC9M3HT2jZb9v2mpiuf-ZwFZuJogYsqZQVzYl8Q", "dq": "U5Tqn4xdHON1UkbULLFIlmJVF3g-I9SdK70x9WaAAKUR1Ys5ffj3y8lPkGUMlTtNf3t4yNFo2lE_6-qvgM4MxQ", "qi": "dhaZmFV6lFH-jD4hb_-GtaMlsk97gqW7zU2gSVZdULGXZsqbQEfm0k34mglOWC6Hxmi1rqQB_HAe9HUyNdHPXg", "kty": "RSA", } self.assertWarns(SecurityWarning, RSAKey.import_key, rsa_jwk) 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_import_invalid_pem_key(self): public_pem = read_key("ec-p256-public.pem") self.assertRaises(InvalidKeyTypeError, RSAKey.import_key, public_pem) 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) authlib-joserfc-aae7743/tests/jws/000077500000000000000000000000001511744432500171635ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jws/__init__.py000066400000000000000000000000001511744432500212620ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jws/test_compact.py000066400000000000000000000106671511744432500222340ustar00rootroot00000000000000from unittest import TestCase from joserfc.jws import ( JWSRegistry, serialize_compact, deserialize_compact, detach_content, ) from joserfc.jwk import OctKey, RSAKey, KeySet from joserfc.errors import ( BadSignatureError, DecodeError, MissingAlgorithmError, UnsupportedAlgorithmError, UnsupportedHeaderError, ExceededSizeError, ) from joserfc.util import urlsafe_b64encode, json_b64encode class TestCompact(TestCase): key = OctKey.import_key("secret") def test_registry_is_none(self): value = serialize_compact({"alg": "HS256"}, b"foo", self.key) expected = "eyJhbGciOiJIUzI1NiJ9.Zm9v.0pehoi-RMZM1jl-4TP_C4Y6BJ-bcmsuzfDyQpkpJkh0" self.assertEqual(value, expected) obj = deserialize_compact(value, self.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): self.assertRaises(UnsupportedAlgorithmError, serialize_compact, {"alg": "HS512"}, b"foo", self.key) self.assertRaises(UnsupportedAlgorithmError, serialize_compact, {"alg": "NOT"}, b"foo", self.key) def test_invalid_length(self): self.assertRaises(DecodeError, deserialize_compact, b"a.b.c.d", self.key) def test_no_invalid_header(self): # invalid base64 value = b"abc.Zm9v.0pehoi" self.assertRaises(DecodeError, deserialize_compact, value, self.key) # no alg value value = b"eyJhIjoiYiJ9.Zm9v.0pehoi" self.assertRaises(MissingAlgorithmError, deserialize_compact, value, self.key) def test_invalid_payload(self): value = b"eyJhbGciOiJIUzI1NiJ9.a$b.0pehoi" self.assertRaises(DecodeError, deserialize_compact, value, self.key) def test_header_exceeded_size_error(self): exceeded_header = json_b64encode({f"a{i}": f"a{i}" for i in range(1000)}) other = urlsafe_b64encode(b"o") fake_jws = exceeded_header + b"." + other + b"." + other self.assertRaises(ExceededSizeError, deserialize_compact, fake_jws, self.key) def test_payload_exceeded_size_error(self): header = json_b64encode({"alg": "HS256"}) exceeded_payload = urlsafe_b64encode(("o" * 10000).encode("utf8")) fake_jws = header + b"." + exceeded_payload + b"." + urlsafe_b64encode(b"o") self.assertRaises(ExceededSizeError, deserialize_compact, fake_jws, self.key) def test_signature_exceeded_size_error(self): header = json_b64encode({"alg": "HS256"}) exceeded_signature = urlsafe_b64encode(("o" * 1000).encode("utf8")) fake_jws = header + b"." + urlsafe_b64encode(b"o") + b"." + exceeded_signature self.assertRaises(ExceededSizeError, deserialize_compact, fake_jws, self.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"} self.assertRaises(UnsupportedHeaderError, serialize_compact, header, b"hi", self.key) registry = JWSRegistry(strict_check_header=False) serialize_compact(header, b"hi", self.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")) def test_detached_content(self): value = detach_content(serialize_compact({"alg": "HS256"}, b"foo", self.key)) expected = "eyJhbGciOiJIUzI1NiJ9..0pehoi-RMZM1jl-4TP_C4Y6BJ-bcmsuzfDyQpkpJkh0" self.assertEqual(value, expected) obj = deserialize_compact(value, self.key, payload=b"foo") self.assertEqual(obj.payload, b"foo") authlib-joserfc-aae7743/tests/jws/test_eddsa.py000066400000000000000000000042631511744432500216610ustar00rootroot00000000000000import typing as t from unittest import TestCase from joserfc import jwt from joserfc.jwk import OKPKey from joserfc.errors import InvalidKeyTypeError, InvalidKeyCurveError, BadSignatureError from tests.base import load_key class TestEdDSA(TestCase): x25519_key = t.cast(OKPKey, load_key("okp-x25519-alice.json")) ed25519_key = t.cast(OKPKey, load_key("okp-ed25519-private.json")) ed448_key = t.cast(OKPKey, load_key("okp-ed448-private.pem")) def test_EdDSA(self): algorithms = ["EdDSA"] encoded_jwt = jwt.encode({"alg": "EdDSA"}, {}, self.ed25519_key, algorithms=algorithms) jwt.decode(encoded_jwt, self.ed25519_key, algorithms=algorithms) self.assertRaises(InvalidKeyTypeError, jwt.decode, encoded_jwt, self.x25519_key, algorithms=algorithms) self.assertRaises(InvalidKeyTypeError, jwt.encode, {"alg": "EdDSA"}, {}, self.x25519_key, algorithms=algorithms) def test_Ed25519(self): algorithms = ["Ed25519"] encoded_jwt = jwt.encode({"alg": "Ed25519"}, {}, self.ed25519_key, algorithms=algorithms) jwt.decode(encoded_jwt, self.ed25519_key, algorithms=algorithms) self.assertRaises( InvalidKeyCurveError, jwt.encode, {"alg": "Ed25519"}, {}, self.ed448_key, algorithms=algorithms ) self.assertRaises(InvalidKeyCurveError, jwt.decode, encoded_jwt, self.ed448_key, algorithms=algorithms) wrong_key = OKPKey.generate_key("Ed25519", private=False) self.assertRaises(BadSignatureError, jwt.decode, encoded_jwt, wrong_key, algorithms=algorithms) def test_Ed448(self): algorithms = ["Ed448"] encoded_jwt = jwt.encode({"alg": "Ed448"}, {}, self.ed448_key, algorithms=algorithms) jwt.decode(encoded_jwt, self.ed448_key, algorithms=algorithms) self.assertRaises( InvalidKeyCurveError, jwt.encode, {"alg": "Ed448"}, {}, self.ed25519_key, algorithms=algorithms ) self.assertRaises(InvalidKeyCurveError, jwt.decode, encoded_jwt, self.ed25519_key, algorithms=algorithms) wrong_key = OKPKey.generate_key("Ed448", private=False) self.assertRaises(BadSignatureError, jwt.decode, encoded_jwt, wrong_key, algorithms=algorithms) authlib-joserfc-aae7743/tests/jws/test_errors.py000066400000000000000000000163701511744432500221170ustar00rootroot00000000000000from 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, InvalidKeyCurveError, 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_unsupported_crit_header(self): header = {"alg": "HS256", "bob": "a", "crit": ["bob"]} self.assertRaises( UnsupportedHeaderError, jws.serialize_compact, header, "i", self.key, ) registry = jws.JWSRegistry( header_registry={ "bob": HeaderParameter("Bob", "str"), } ) # allow with custom header registry jws.serialize_compact(header, "i", self.key, registry=registry) 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(InvalidKeyCurveError, 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) authlib-joserfc-aae7743/tests/jws/test_examples.py000066400000000000000000000031151511744432500224120ustar00rootroot00000000000000from tests.base import TestFixture, load_key from joserfc.jwk import OctKey from joserfc.jws import HeaderDict 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: HeaderDict = {"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") authlib-joserfc-aae7743/tests/jws/test_json.py000066400000000000000000000173651511744432500215610ustar00rootroot00000000000000from unittest import TestCase from joserfc.jws import ( HeaderDict, FlattenedJSONSerialization, GeneralJSONSerialization, JWSRegistry, serialize_json, deserialize_json, detach_content, ) from joserfc.jwk import RSAKey, OctKey, KeySet from joserfc.errors import ( DecodeError, BadSignatureError, UnsupportedHeaderError, UnsupportedAlgorithmError, ExceededSizeError, ) from joserfc.util import json_b64encode, urlsafe_b64encode, to_str from tests.base import load_key class TestJSON(TestCase): key = OctKey.import_key("secret") def test_serialize_json(self): key: RSAKey = load_key("rsa-openssl-private.pem") member: HeaderDict = {"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: HeaderDict = {"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: HeaderDict = {"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: HeaderDict = {"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: HeaderDict = {"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: HeaderDict = {"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: HeaderDict = {"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"}) def test_strict_check_header(self): member: HeaderDict = {"protected": {"alg": "HS256", "custom": "hi"}} self.assertRaises(UnsupportedHeaderError, serialize_json, member, b"hi", self.key) registry = JWSRegistry(strict_check_header=False) serialize_json(member, b"hi", self.key, registry=registry) def test_unsupported_algorithm(self): member: HeaderDict = {"protected": {"alg": "HS256"}} value = serialize_json(member, b"hi", self.key) registry = JWSRegistry(algorithms=["HS512"]) self.assertRaises(UnsupportedAlgorithmError, deserialize_json, value, self.key, registry=registry) def test_prevent_overwrite_header(self): member: HeaderDict = {"protected": {"alg": "HS256", "kid": "a"}} value = serialize_json(member, b"hello", self.key) value["header"] = {"kid": "b"} obj = deserialize_json(value, self.key) self.assertEqual(obj.headers()["kid"], "a") def test_header_exceeded_size_error(self): data: FlattenedJSONSerialization = { "protected": to_str(json_b64encode({f"a{i}": "a" * i for i in range(100)})), "payload": to_str(json_b64encode({"a": "a"})), "signature": to_str(urlsafe_b64encode(b"o")), } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) data: GeneralJSONSerialization = { "payload": to_str(json_b64encode({"a": "a"})), "signatures": [ { "protected": to_str(json_b64encode({f"a{i}": "a" * i for i in range(100)})), "signature": to_str(urlsafe_b64encode(b"o")), } ], } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) def test_payload_exceeded_size_error(self): data: FlattenedJSONSerialization = { "protected": to_str(json_b64encode({"alg": "HS256"})), "payload": to_str(json_b64encode({f"a{i}": "a" * i for i in range(1000)})), "signature": to_str(urlsafe_b64encode(b"o")), } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) data: GeneralJSONSerialization = { "payload": to_str(json_b64encode({f"a{i}": "a" * i for i in range(1000)})), "signatures": [ { "protected": to_str(json_b64encode({"alg": "HS256"})), "signature": to_str(urlsafe_b64encode(b"o")), } ], } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) def test_signature_exceeded_size_error(self): data: FlattenedJSONSerialization = { "protected": to_str(json_b64encode({"alg": "HS256"})), "payload": to_str(json_b64encode({"a": "a"})), "signature": to_str(urlsafe_b64encode(("o" * 1000).encode("utf-8"))), } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) data: GeneralJSONSerialization = { "payload": to_str(json_b64encode({"a": "a"})), "signatures": [ { "protected": to_str(json_b64encode({"alg": "HS256"})), "signature": to_str(urlsafe_b64encode(("o" * 1000).encode("utf-8"))), } ], } self.assertRaises(ExceededSizeError, deserialize_json, data, self.key) authlib-joserfc-aae7743/tests/jws/test_registry.py000066400000000000000000000076521511744432500224560ustar00rootroot00000000000000import unittest from joserfc.jws import JWSRegistry from joserfc.jwk import OctKey, RSAKey, ECKey, OKPKey, KeySet class JWSRegistryTest(unittest.TestCase): oct_key = OctKey.generate_key() rsa_key = RSAKey.generate_key() ec_key = ECKey.generate_key() okp_key = OKPKey.generate_key() def test_guess_recommended_algorithm(self): alg = JWSRegistry.guess_algorithm(self.oct_key, JWSRegistry.Strategy.RECOMMENDED) self.assertEqual(alg.name, "HS256") alg = JWSRegistry.guess_algorithm(self.rsa_key, JWSRegistry.Strategy.RECOMMENDED) self.assertEqual(alg.name, "RS256") alg = JWSRegistry.guess_algorithm(self.ec_key, JWSRegistry.Strategy.RECOMMENDED) self.assertEqual(alg.name, "ES256") alg = JWSRegistry.guess_algorithm(self.okp_key, JWSRegistry.Strategy.RECOMMENDED) self.assertEqual(alg, None) def test_guess_security_algorithm(self): alg = JWSRegistry.guess_algorithm(self.oct_key, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "HS512") alg = JWSRegistry.guess_algorithm(self.rsa_key, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "RS512") alg = JWSRegistry.guess_algorithm(self.ec_key, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "ES256") ec521 = ECKey.generate_key("P-521") alg = JWSRegistry.guess_algorithm(ec521, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "ES512") alg = JWSRegistry.guess_algorithm(self.okp_key, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "EdDSA") def test_filter_algorithms_default_names(self): all_names = list(JWSRegistry.algorithms.keys()) explicit = JWSRegistry.filter_algorithms(self.rsa_key, all_names) default = JWSRegistry.filter_algorithms(self.rsa_key) self.assertEqual(explicit, default) def test_filter_algorithms_ed25519(self): """Ed25519 keys should only be compatible with EdDSA and Ed25519, not Ed448.""" ed25519_key = OKPKey.generate_key("Ed25519") algs = JWSRegistry.filter_algorithms(ed25519_key) names = [alg.name for alg in algs] self.assertIn("EdDSA", names) self.assertIn("Ed25519", names) self.assertNotIn("Ed448", names) def test_filter_algorithms_ed448(self): """Ed448 keys should only be compatible with EdDSA and Ed448, not Ed25519.""" ed448_key = OKPKey.generate_key("Ed448") algs = JWSRegistry.filter_algorithms(ed448_key) names = [alg.name for alg in algs] self.assertIn("EdDSA", names) self.assertIn("Ed448", names) self.assertNotIn("Ed25519", names) def test_filter_algorithms_with_key_set(self): """filter_algorithms should support KeySet and combine algorithms from all keys.""" rsa_key1 = RSAKey.generate_key() rsa_key2 = RSAKey.generate_key() ec_key = ECKey.generate_key("P-256") key_set = KeySet([rsa_key1, rsa_key2, ec_key]) algs = JWSRegistry.filter_algorithms(key_set, JWSRegistry.algorithms.keys()) names = [alg.name for alg in algs] self.assertIn("RS256", names) self.assertIn("ES256", names) self.assertNotIn("ES384", names) self.assertEqual(names.count("RS256"), 1) def test_guess_algorithm_with_key_set(self): """guess_algorithm should find the best algorithm across all keys in the KeySet.""" rsa_key = RSAKey.generate_key() ec_key = ECKey.generate_key("P-256") key_set = KeySet([rsa_key, ec_key]) # RS256 comes before ES256 in the recommended list alg = JWSRegistry.guess_algorithm(key_set, JWSRegistry.Strategy.RECOMMENDED) self.assertEqual(alg.name, "RS256") # RS512 has the highest algorithm_security (512) among available algorithms alg = JWSRegistry.guess_algorithm(key_set, JWSRegistry.Strategy.SECURITY) self.assertEqual(alg.name, "RS512") authlib-joserfc-aae7743/tests/jws/test_rfc7797.py000066400000000000000000000073401511744432500217100ustar00rootroot00000000000000from joserfc.jwk import OctKey from joserfc.errors import ( DecodeError, MissingAlgorithmError, BadSignatureError, InvalidHeaderValueError, MissingCritHeaderError, UnsupportedHeaderError, ) from joserfc.util import to_bytes from joserfc.jws import HeaderDict 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 = jws.serialize_compact(protected, payload, default_key) self.assertEqual(value1, data["compact"]) obj1 = jws.deserialize_compact(value1, default_key, payload=payload) self.assertEqual(obj1.headers(), protected) self.assertEqual(obj1.payload, to_bytes(payload)) value2 = jws.serialize_json({"protected": protected}, payload, default_key) self.assertEqual(value2, data["flattened_json"]) obj2 = jws.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(MissingCritHeaderError, jws.serialize_compact, protected, "i", default_key) def test_invalid_b64_value(self): protected = {"alg": "HS256", "b64": "true", "crit": ["b64"]} self.assertRaises(InvalidHeaderValueError, jws.serialize_compact, protected, "i", default_key) def test_compact_invalid_value_length(self): self.assertRaises(DecodeError, jws.deserialize_compact, b"a.b.c.d.e", default_key) def test_invalid_header(self): self.assertRaises(DecodeError, jws.deserialize_compact, b"a.b.c", default_key) def test_compact_missing_alg(self): self.assertRaises(MissingAlgorithmError, jws.deserialize_compact, b"e30.a.b", default_key) def test_compact_bad_signature(self): protected = {"alg": "HS256", "b64": False, "crit": ["b64"]} value = jws.serialize_compact(protected, "hello", default_key) key2 = OctKey.import_key("secret") self.assertRaises(BadSignatureError, jws.deserialize_compact, value, key2) def test_compact_use_registry(self): protected = {"alg": "HS256", "b64": True, "crit": ["b64"]} value = jws.serialize_compact(protected, "hello", default_key) obj = jws.deserialize_compact(value, default_key) self.assertEqual(obj.protected, protected) protected = {"alg": "HS256"} value = jws.serialize_compact(protected, "hello", default_key) obj = jws.deserialize_compact(value, default_key) self.assertEqual(obj.protected, protected) def test_json_without_protected_header(self): header = {"alg": "HS256", "b64": False, "crit": ["b64"]} member: HeaderDict = {"header": header} self.assertRaises(UnsupportedHeaderError, jws.serialize_json, member, "hello", default_key) def test_general_json(self): member: HeaderDict = {"protected": {"alg": "HS256"}} value = jws.serialize_json([member], "hello", default_key) obj = jws.deserialize_json(value, default_key) self.assertFalse(obj.flattened) def test_json_bad_signature(self): member: HeaderDict = {"protected": {"alg": "HS256", "b64": False, "crit": ["b64"]}} value = jws.serialize_json(member, "hello", default_key) key2 = OctKey.import_key("secret") self.assertRaises(BadSignatureError, jws.deserialize_json, value, key2) TestRFC7797.load_fixture("jws_rfc7797.json") authlib-joserfc-aae7743/tests/jwt/000077500000000000000000000000001511744432500171645ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwt/__init__.py000066400000000000000000000000001511744432500212630ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/jwt/test_claims.py000066400000000000000000000233311511744432500220470ustar00rootroot00000000000000import 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): claims_requests = jwt.JWTClaimsRegistry(leeway=500) now = int(time.time()) claims_requests.validate({"iat": now}) self.assertRaises(InvalidTokenError, claims_requests.validate, {"iat": now + 1000}) def test_validate_nbf(self): claims_requests = jwt.JWTClaimsRegistry(leeway=500) now = int(time.time()) 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)}) def test_validate_list_inclusion(self): # Case 1: use option value claims_requests = jwt.JWTClaimsRegistry(custom_claim={"essential": True, "value": "a"}) self.assertRaises(MissingClaimError, claims_requests.validate, {"iss": "a"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": "b"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": ["b"]}) claims_requests.validate({"custom_claim": "a"}) claims_requests.validate({"custom_claim": ["a"]}) # Case 2: use option values claims_requests = jwt.JWTClaimsRegistry(custom_claim={"essential": True, "values": ["a", "b"]}) self.assertRaises(MissingClaimError, claims_requests.validate, {"iss": "a"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": "c"}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": ["c"]}) claims_requests.validate({"custom_claim": "a"}) claims_requests.validate({"custom_claim": "b"}) claims_requests.validate({"custom_claim": ["a"]}) claims_requests.validate({"custom_claim": ["b"]}) claims_requests.validate({"custom_claim": ["a", "b"]}) claims_requests.validate({"custom_claim": ["c", "a"]}) # Case 3: do not validate claims_requests = jwt.JWTClaimsRegistry() claims_requests.validate({"custom_claim": "a"}) # Case 4: essential claim without value(s) claims_requests = jwt.JWTClaimsRegistry(custom_claim={"essential": True}) claims_requests.validate({"custom_claim": "a"}) def test_validate_allow_blank(self): # Case 1: allow blank value claims_requests = jwt.JWTClaimsRegistry(custom_claim={"essential": True, "allow_blank": True}) self.assertRaises(MissingClaimError, claims_requests.validate, {"custom_claim": None}) claims_requests.validate({"custom_claim": ""}) claims_requests.validate({"custom_claim": []}) claims_requests.validate({"custom_claim": {}}) # Case 2: allow blank value without essential claims_requests = jwt.JWTClaimsRegistry(custom_claim={"allow_blank": True}) claims_requests.validate({"custom_claim": None}) claims_requests.validate({"custom_claim": ""}) claims_requests.validate({"custom_claim": []}) claims_requests.validate({"custom_claim": {}}) # Case 3: do not allow blank value claims_requests = jwt.JWTClaimsRegistry(custom_claim={"essential": True, "allow_blank": False}) self.assertRaises(MissingClaimError, claims_requests.validate, {"custom_claim": None}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": ""}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": []}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": {}}) # Case 4: do not allow blank value without essential claims_requests = jwt.JWTClaimsRegistry(custom_claim={"allow_blank": False}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": None}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": ""}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": []}) self.assertRaises(InvalidClaimError, claims_requests.validate, {"custom_claim": {}}) authlib-joserfc-aae7743/tests/jwt/test_fixtures.py000066400000000000000000000016141511744432500224500ustar00rootroot00000000000000from 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") authlib-joserfc-aae7743/tests/jwt/test_jwt.py000066400000000000000000000075651511744432500214160ustar00rootroot00000000000000from unittest import TestCase from joserfc import jws, jwe, jwt from joserfc.jwk import OctKey, GuestProtocol, Key, import_key from joserfc.errors import ( InvalidPayloadError, MissingClaimError, UnsupportedHeaderError, DecodeError, ) def use_embedded_jwk(obj: GuestProtocol) -> Key: headers = obj.headers() return import_key(headers["jwk"]) class TestJWT(TestCase): oct_key = OctKey.generate_key() def test_default_type(self): data = jwt.encode({"alg": "HS256"}, {"sub": "a"}, self.oct_key) token = jwt.decode(data, self.oct_key) self.assertEqual(token.header["typ"], "JWT") data = jwt.encode({"alg": "HS256"}, {"sub": "a"}, self.oct_key, default_type=None) token = jwt.decode(data, self.oct_key) self.assertNotIn("typ", token.header) data = jwt.encode({"alg": "HS256"}, {"sub": "a"}, self.oct_key, default_type="jwt+at") token = jwt.decode(data, self.oct_key) self.assertEqual(token.header["typ"], "jwt+at") def test_invalid_payload(self): data = jws.serialize_compact({"alg": "HS256"}, b"hello", self.oct_key) self.assertRaises(InvalidPayloadError, jwt.decode, data, self.oct_key) def test_claims_registry(self): data = jwt.encode({"alg": "HS256"}, {"sub": "a"}, self.oct_key) token = jwt.decode(data, self.oct_key) claims_registry = jwt.JWTClaimsRegistry(iss={"essential": True}) self.assertRaises(MissingClaimError, claims_registry.validate, token.claims) data = jwt.encode({"alg": "HS256"}, {"iss": "a"}, self.oct_key) obj = jwt.decode(data, self.oct_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(), ) def test_with_embedded_jwk(self): value = ( "eyJqd2siOnsiY3J2IjoiUC0yNTYiLCJ4IjoiVU05ZzVuS25aWFlvdldBbE" "03NmNMejl2VG96UmpfX0NIVV9kT2wtZ09vRSIsInkiOiJkczhhZVF3MWwy" "Y0RDQTdiQ2tPTnZ3REtwWEFidFhqdnFDbGVZSDhXc19VIiwia3R5IjoiRU" "MifSwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1Z" "XIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSIsImlhdCI6MTYwNDU" "4MDc5NH0.60boak3_dErnW47ZPty1C0nrjeVq86EN_eK0GOq6K8w2OA0th" "KoBxFK4j-NuU9yZ_A9UKGxPT_G87DladBaV9g" ) token = jwt.decode(value, use_embedded_jwk) self.assertEqual(token.claims["iss"], "urn:example:issuer") authlib-joserfc-aae7743/tests/keys/000077500000000000000000000000001511744432500173335ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/keys/RFC7516-A.1.3.json000066400000000000000000000031651511744432500216460ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7516-A.2.3.json000066400000000000000000000031651511744432500216470ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-EC-108.json000066400000000000000000000005031511744432500217470ustar00rootroot00000000000000{ "kty": "EC", "kid": "peregrin.took@tuckborough.example", "use": "enc", "crv": "P-384", "x": "YU4rRUzdmVqmRtWOs2OpDE_T5fsNIodcG8G5FWPrTPMyxpzsSOGaQLpe2FpxBmu2", "y": "A8-yxCHxkfBz3hKZfI1jUYMjUhsEveZ9THuwFjH2sCNdtksRJU7D5-SkgaFL1ETP", "d": "iTx2pk7wW-GqJkHcEkFQb2EFyYcO7RugmaW3mRrQVAOUiPommT0IdnYK2xDlZh-j" } authlib-joserfc-aae7743/tests/keys/RFC7520-EC-120.json000066400000000000000000000004071511744432500217440ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-EC-private.json000066400000000000000000000006101511744432500231100ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-EC-public.json000066400000000000000000000004451511744432500227220ustar00rootroot00000000000000{ "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" } authlib-joserfc-aae7743/tests/keys/RFC7520-RSA-73.json000066400000000000000000000032601511744432500220310ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-RSA-84.json000066400000000000000000000063071511744432500220400ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-RSA-private.json000066400000000000000000000032601511744432500232520ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-RSA-public.json000066400000000000000000000006761511744432500230660ustar00rootroot00000000000000{ "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" } authlib-joserfc-aae7743/tests/keys/RFC7520-oct-130.json000066400000000000000000000002111511744432500222340ustar00rootroot00000000000000{ "kty": "oct", "kid": "77c7e2b8-6e13-45cf-8672-617b5b45243a", "use": "enc", "alg": "A128GCM", "k": "XctOhJAkA-pD9Lh7ZgW_2A" } authlib-joserfc-aae7743/tests/keys/RFC7520-oct-enc.json000066400000000000000000000002361511744432500225050ustar00rootroot00000000000000{ "kty": "oct", "kid": "1e571774-2e08-40da-8308-e8d68773842d", "use": "enc", "alg": "A256GCM", "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8" } authlib-joserfc-aae7743/tests/keys/RFC7520-oct-sig.json000066400000000000000000000002341511744432500225200ustar00rootroot00000000000000{ "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" } authlib-joserfc-aae7743/tests/keys/__init__.py000066400000000000000000000004371511744432500214500ustar00rootroot00000000000000import 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 authlib-joserfc-aae7743/tests/keys/ec-p256-alice.json000066400000000000000000000003061511744432500223610ustar00rootroot00000000000000{ "kty": "EC", "crv": "P-256", "x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", "y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", "d": "Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg" } authlib-joserfc-aae7743/tests/keys/ec-p256-bob.json000066400000000000000000000003061511744432500220460ustar00rootroot00000000000000{ "kty": "EC", "crv": "P-256", "x": "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", "y": "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", "d": "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" } authlib-joserfc-aae7743/tests/keys/ec-p256-private.pem000066400000000000000000000003431511744432500225670ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHcCAQEEIBnRS4Tf1PY6Jb7QOwAM7OWUOMJTBenEWRvGBCGgctBfoAoGCCqGSM49 AwEHoUQDQgAE3r15c+Yd+0GXKysfWtwkqF7k12ylNE9LdfRP4TfkUcJSQXyGQjcx U8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END EC PRIVATE KEY----- authlib-joserfc-aae7743/tests/keys/ec-p256-public.pem000066400000000000000000000002621511744432500223730ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3r15c+Yd+0GXKysfWtwkqF7k12yl NE9LdfRP4TfkUcJSQXyGQjcxU8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END PUBLIC KEY----- authlib-joserfc-aae7743/tests/keys/ec-p384-private.pem000066400000000000000000000004401511744432500225670ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIGkAgEBBDDQy7nBIq/aaPR980Wfqk5HqU7qVjo7fvKEeYY/8XxNE1BKUx5VkrSj G2g5GqgwRtKgBwYFK4EEACKhZANiAARuuP3WJg9DRzKCZ/xsiA66fJ1NoQmK4d7b 1+t9D5f+srq3f9Ttj/NWdn/WaVDf1ectfSQCyInrC8QXBhGqJj0GNIHzvAykCN0H KS5B9yM0oOKMnSGSklLrOXLQKagxLSU= -----END EC PRIVATE KEY----- authlib-joserfc-aae7743/tests/keys/ec-p384-public.pem000066400000000000000000000003271511744432500223770ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbrj91iYPQ0cygmf8bIgOunydTaEJiuHe 29frfQ+X/rK6t3/U7Y/zVnZ/1mlQ39XnLX0kAsiJ6wvEFwYRqiY9BjSB87wMpAjd BykuQfcjNKDijJ0hkpJS6zly0CmoMS0l -----END PUBLIC KEY----- authlib-joserfc-aae7743/tests/keys/ec-p512-private.pem000066400000000000000000000005511511744432500225630ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MIHbAgEBBEFvFujwdb3ZFYnWnUZrFobrksVQfpDGFJ9Zt1ofpUrDBjBd4Z6rNB+x K5OrfJPm2WidZxzsU69J9cCx/ntANMMUWaAHBgUrgQQAI6GBiQOBhgAEANoDiaaU xmbFy1RrRNOSCsOp5lHj3ugLUnoK/MZHTLGL8UNVsw03K4aqqwVvA43CvQiQZE4t gZAEYR/n+mCoXsutAYmlEpwe1e4VZTklnO+WULy8anV5yIjrmdwIDVvJ1IyJuBDK ZO7SyxCnL6S/OW+WjPU9T6ZXcgNRBVaY40zwQ3zh -----END EC PRIVATE KEY----- authlib-joserfc-aae7743/tests/keys/ec-p512-public.pem000066400000000000000000000004141511744432500223650ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA2gOJppTGZsXLVGtE05IKw6nmUePe 6AtSegr8xkdMsYvxQ1WzDTcrhqqrBW8DjcK9CJBkTi2BkARhH+f6YKhey60BiaUS nB7V7hVlOSWc75ZQvLxqdXnIiOuZ3AgNW8nUjIm4EMpk7tLLEKcvpL85b5aM9T1P pldyA1EFVpjjTPBDfOE= -----END PUBLIC KEY----- authlib-joserfc-aae7743/tests/keys/ec-secp256k1-private.pem000066400000000000000000000003371511744432500235210ustar00rootroot00000000000000-----BEGIN EC PRIVATE KEY----- MHQCAQEEIPuQY2DwDanipOspQMvzw30TvLlySoRiF1kogPK5oB5DoAcGBSuBBAAK oUQDQgAErZ9DjXDbhhPcFzBlHP2yG6UzFMw6bC9XchzLAbA5rUAZDBC7IpJKX97G x6Xy/4MTP9/Wevl2SJ4qCb79pHSIkg== -----END EC PRIVATE KEY----- authlib-joserfc-aae7743/tests/keys/ec-secp256k1-public.pem000066400000000000000000000002561511744432500233250ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAErZ9DjXDbhhPcFzBlHP2yG6UzFMw6bC9X chzLAbA5rUAZDBC7IpJKX97Gx6Xy/4MTP9/Wevl2SJ4qCb79pHSIkg== -----END PUBLIC KEY----- authlib-joserfc-aae7743/tests/keys/firebase-cert.pem000066400000000000000000000021631511744432500225530ustar00rootroot00000000000000-----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----- authlib-joserfc-aae7743/tests/keys/okp-ed25519-private.json000066400000000000000000000002771511744432500234710ustar00rootroot00000000000000{"crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "d": "gUF17HCe-pbN7Ej2rDSXl-e7uSj7rQW5u2dNu0KINP0", "kty": "OKP", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y"} authlib-joserfc-aae7743/tests/keys/okp-ed25519-public.json000066400000000000000000000002131511744432500232630ustar00rootroot00000000000000{"crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "kty": "OKP", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y"} authlib-joserfc-aae7743/tests/keys/okp-ed448-private.pem000066400000000000000000000002341511744432500231240ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOaVsPKMXOBfq9aHlDEaMlBY+FR63hwrINHa2X74uHXUr 3/VXE8eMhrr8stXn41CQKqVmFEeL5Uj5Gg== -----END PRIVATE KEY----- authlib-joserfc-aae7743/tests/keys/okp-ed448-public.pem000066400000000000000000000002221511744432500227250ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MEMwBQYDK2VxAzoAcLccm4NG6CZghPOBE80BmngFHqIZEGL7nNJEpHaq4WTBVEDW UoHLNFMSBhwF6bE+7WZSE++fdBwA -----END PUBLIC KEY----- authlib-joserfc-aae7743/tests/keys/okp-x25519-alice.json000066400000000000000000000002221511744432500227410ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "Knbm_BcdQr7WIoz-uqit9M0wbcfEr6y-9UfIZ8QnBD4", "d": "i9KuFhSzEBsiv3PKVL5115OCdsqQai5nj_Flzfkw5jU" } authlib-joserfc-aae7743/tests/keys/okp-x25519-bob.json000066400000000000000000000002221511744432500224260ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "BT7aR0ItXfeDAldeeOlXL_wXqp-j5FltT0vRSG16kRw", "d": "1gDirl_r_Y3-qUa3WXHgEXrrEHngWThU3c9zj9A2uBg" } authlib-joserfc-aae7743/tests/keys/okp-x25519-charlie.json000066400000000000000000000002221511744432500232730ustar00rootroot00000000000000{ "kty": "OKP", "crv": "X25519", "x": "q-LsvU772uV_2sPJhfAIq-3vnKNVefNoIlvyvg1hrnE", "d": "Jcv8gklhMjC0b-lsk5onBbppWAx5ncNtbM63Jr9xBQE" } authlib-joserfc-aae7743/tests/keys/rsa-openssl-private.pem000066400000000000000000000062531511744432500237620ustar00rootroot00000000000000-----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----- authlib-joserfc-aae7743/tests/keys/rsa-openssl-public.pem000066400000000000000000000014401511744432500235570ustar00rootroot00000000000000-----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----- authlib-joserfc-aae7743/tests/keys/ssh-ecdsa-public.pem000066400000000000000000000002411511744432500231610ustar00rootroot00000000000000ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLTEVnhsZrRnFhOlXN68wUslJD6/Xo5N4TprdkyiG3UZYKN7tT8ot0ZLP7XOv3lusPd9+A128hRjYhKQIFHPCyY= authlib-joserfc-aae7743/tests/keys/ssh-ed25519-public.pem000066400000000000000000000001211511744432500230750ustar00rootroot00000000000000ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILuDaT2DEQNFTGvcnOsDp+cvSppjVRPajco10eDh76LB authlib-joserfc-aae7743/tests/keys/ssh-rsa-private.pem000066400000000000000000000064651511744432500231010ustar00rootroot00000000000000-----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----- authlib-joserfc-aae7743/tests/keys/ssh-rsa-public.pem000066400000000000000000000013421511744432500226720ustar00rootroot00000000000000ssh-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 authlib-joserfc-aae7743/tests/rfc7520/000077500000000000000000000000001511744432500174505ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/rfc7520/__init__.py000066400000000000000000000000001511744432500215470ustar00rootroot00000000000000authlib-joserfc-aae7743/tests/rfc7520/test_jwe.py000066400000000000000000000156171511744432500216600ustar00rootroot00000000000000import 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") authlib-joserfc-aae7743/tests/rfc7520/test_jwk.py000066400000000000000000000117041511744432500216570ustar00rootroot00000000000000from joserfc.jwk import ECKey, RSAKey, OctKey def test_ec_key(): # https://datatracker.ietf.org/doc/html/rfc7520#section-3.1 public_jwk = { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", } # https://datatracker.ietf.org/doc/html/rfc7520#section-3.2 private_key = ECKey.import_key( { "kty": "EC", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt", } ) output = private_key.as_dict(private=False) assert output == public_jwk def test_rsa_key(): # https://datatracker.ietf.org/doc/html/rfc7520#section-3.3 public_jwk = { "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": ( "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw" ), "e": "AQAB", } # https://datatracker.ietf.org/doc/html/rfc7520#section-3.4 private_jwk = { "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": ( "n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT" "-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqV" "wGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-" "oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde" "3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuC" "LqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5g" "HdrNP5zw" ), "e": "AQAB", "d": ( "bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78e" "iZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRld" "Y7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-b" "MwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU" "6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDj" "d18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOc" "OpBrQzwQ" ), "p": ( "3Slxg_DwTXJcb6095RoXygQCAZ5RnAvZlno1yhHtnUex_fp7AZ_9nR" "aO7HX_-SFfGQeutao2TDjDAWU4Vupk8rw9JR0AzZ0N2fvuIAmr_WCsmG" "peNqQnev1T7IyEsnh8UMt-n5CafhkikzhEsrmndH6LxOrvRJlsPp6Zv8" "bUq0k" ), "q": ( "uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT" "8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7an" "V5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0" "s7pFc" ), "dp": ( "B8PVvXkvJrj2L-GYQ7v3y9r6Kw5g9SahXBwsWUzp19TVlgI-YV85q" "1NIb1rxQtD-IsXXR3-TanevuRPRt5OBOdiMGQp8pbt26gljYfKU_E9xn" "-RULHz0-ed9E9gXLKD4VGngpz-PfQ_q29pk5xWHoJp009Qf1HvChixRX" "59ehik" ), "dq": ( "CLDmDGduhylc9o7r84rEUVn7pzQ6PF83Y-iBZx5NT-TpnOZKF1pEr" "AMVeKzFEl41DlHHqqBLSM0W1sOFbwTxYWZDm6sI6og5iTbwQGIC3gnJK" "bi_7k_vJgGHwHxgPaX2PnvP-zyEkDERuf-ry4c_Z11Cq9AqC2yeL6kdK" "T1cYF8" ), "qi": ( "3PiqvXQN0zwMeE-sBvZgi289XP9XCQF3VWqPzMKnIgQp7_Tugo6-N" "ZBKCQsMf3HaEGBjTVJs_jcK8-TRXvaKe-7ZMaQj8VfBdYkssbu0NKDDh" "jJ-GtiseaDVWt7dcH0cfwxgFUHpQh7FoCrjFJ6h6ZEpMF6xmujs4qMpP" "z8aaI4" ), } private_key = RSAKey.import_key(private_jwk) output = private_key.as_dict(private=True) assert output == private_jwk output = private_key.as_dict(private=False) assert output == public_jwk def test_oct_keys(): jwk1 = { "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg", } key1 = OctKey.import_key(jwk1) assert key1.as_dict() == jwk1 jwk2 = { "kty": "oct", "kid": "1e571774-2e08-40da-8308-e8d68773842d", "use": "enc", "alg": "A256GCM", "k": "AAPapAv4LbFbiVawEjagUBluYqN5rhna-8nuldDvOx8", } key2 = OctKey.import_key(jwk2) assert key2.as_dict() == jwk2 authlib-joserfc-aae7743/tests/rfc7520/test_jws.py000066400000000000000000000205041511744432500216650ustar00rootroot00000000000000from 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") authlib-joserfc-aae7743/tests/test_util.py000066400000000000000000000021171511744432500207470ustar00rootroot00000000000000import 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_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())