././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1634399673.8638868 PyJWT-2.3.0/0000755000076500000240000000000000000000000013375 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634387019.0 PyJWT-2.3.0/.pre-commit-config.yaml0000644000076500000240000000144600000000000017663 0ustar00jpadillastaff00000000000000repos: - repo: https://github.com/psf/black rev: 21.9b0 hooks: - id: black args: ["--target-version=py36"] - repo: https://github.com/asottile/blacken-docs rev: v1.11.0 hooks: - id: blacken-docs args: ["--target-version=py36"] - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 language_version: python3.8 - repo: https://github.com/PyCQA/isort rev: 5.9.3 hooks: - id: isort - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: debug-statements - repo: https://github.com/mgedmin/check-manifest rev: "0.47" hooks: - id: check-manifest args: [--no-build-isolation] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1604321310.0 PyJWT-2.3.0/AUTHORS.rst0000644000076500000240000000050200000000000015251 0ustar00jpadillastaff00000000000000Authors ======= ``pyjwt`` is currently written and maintained by `Jose Padilla `_. Originally written and maintained by `Jeff Lindsay `_. A full list of contributors can be found on GitHub’s `overview `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399631.0 PyJWT-2.3.0/CHANGELOG.rst0000644000076500000240000005622500000000000015430 0ustar00jpadillastaff00000000000000Changelog ========= All notable changes to this project will be documented in this file. This project adheres to `Semantic Versioning `__. `Unreleased `__ ----------------------------------------------------------------------- Changed ~~~~~~~ Fixed ~~~~~ Added ~~~~~ `v2.3.0 `__ ----------------------------------------------------------------------- Fixed ~~~~~ - Revert "Remove arbitrary kwargs." `#701 `__ Added ~~~~~ - Add exception chaining `#702 `__ `v2.2.0 `__ ----------------------------------------------------------------------- Changed ~~~~~~~ - Remove arbitrary kwargs. `#657 `__ - Use timezone package as Python 3.5+ is required. `#694 `__ Fixed ~~~~~ - Assume JWK without the "use" claim is valid for signing as per RFC7517 `#668 `__ - Prefer `headers["alg"]` to `algorithm` in `jwt.encode()`. `#673 `__ - Fix aud validation to support {'aud': null} case. `#670 `__ - Make `typ` optional in JWT to be compliant with RFC7519. `#644 `__ - Remove upper bound on cryptography version. `#693 `__ Added ~~~~~ - Add support for Ed448/EdDSA. `#675 `__ `v2.1.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Allow claims validation without making JWT signature validation mandatory. `#608 `__ Fixed ~~~~~ - Remove padding from JWK test data. `#628 `__ - Make `kty` mandatory in JWK to be compliant with RFC7517. `#624 `__ - Allow JWK without `alg` to be compliant with RFC7517. `#624 `__ - Allow to verify with private key on ECAlgorithm, as well as on Ed25519Algorithm. `#645 `__ Added ~~~~~ - Add caching by default to PyJWKClient `#611 `__ - Add missing exceptions.InvalidKeyError to jwt module __init__ imports `#620 `__ - Add support for ES256K algorithm `#629 `__ - Add `from_jwk()` to Ed25519Algorithm `#621 `__ - Add `to_jwk()` to Ed25519Algorithm `#643 `__ - Export `PyJWK` and `PyJWKSet` `#652 `__ `v2.0.1 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Rename CHANGELOG.md to CHANGELOG.rst and include in docs `#597 `__ Fixed ~~~~~ - Fix `from_jwk()` for all algorithms `#598 `__ Added ~~~~~ `v2.0.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ Drop support for Python 2 and Python 3.0-3.5 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Python 3.5 is EOL so we decide to drop its support. Version ``1.7.1`` is the last one supporting Python 3.0-3.5. Require cryptography >= 3 ^^^^^^^^^^^^^^^^^^^^^^^^^ Drop support for PyCrypto and ECDSA ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ We've kept this around for a long time, mostly for environments that didn't allow installing cryptography. Drop CLI ^^^^^^^^ Dropped the included cli entry point. Improve typings ^^^^^^^^^^^^^^^ We no longer need to use mypy Python 2 compatibility mode (comments) ``jwt.encode(...)`` return type ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Tokens are returned as string instead of a byte string Dropped deprecated errors ^^^^^^^^^^^^^^^^^^^^^^^^^ Removed ``ExpiredSignature``, ``InvalidAudience``, and ``InvalidIssuer``. Use ``ExpiredSignatureError``, ``InvalidAudienceError``, and ``InvalidIssuerError`` instead. Dropped deprecated ``verify_expiration`` param in ``jwt.decode(...)`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use ``jwt.decode(encoded, key, algorithms=["HS256"], options={"verify_exp": False})`` instead. Dropped deprecated ``verify`` param in ``jwt.decode(...)`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use ``jwt.decode(encoded, key, options={"verify_signature": False})`` instead. Require explicit ``algorithms`` in ``jwt.decode(...)`` by default ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example: ``jwt.decode(encoded, key, algorithms=["HS256"])``. Dropped deprecated ``require_*`` options in ``jwt.decode(...)`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For example, instead of ``jwt.decode(encoded, key, algorithms=["HS256"], options={"require_exp": True})``, use ``jwt.decode(encoded, key, algorithms=["HS256"], options={"require": ["exp"]})``. Added ~~~~~ Introduce better experience for JWKs ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Introduce ``PyJWK``, ``PyJWKSet``, and ``PyJWKClient``. .. code:: python import jwt from jwt import PyJWKClient token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA" kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" jwks_client = PyJWKClient(url) signing_key = jwks_client.get_signing_key_from_jwt(token) data = jwt.decode( token, signing_key.key, algorithms=["RS256"], audience="https://expenses-api", options={"verify_exp": False}, ) print(data) Support for JWKs containing ECDSA keys ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Add support for Ed25519 / EdDSA ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pull Requests ~~~~~~~~~~~~~ - Add PyPy3 to the test matrix (#550) by @jdufresne - Require tweak (#280) by @psafont - Decode return type is dict[str, Any] (#393) by @jacopofar - Fix linter error in test\_cli (#414) by @jaraco - Run mypy with tox (#421) by @jpadilla - Document (and prefer) pyjwt[crypto] req format (#426) by @gthb - Correct type for json\_encoder argument (#438) by @jdufresne - Prefer https:// links where available (#439) by @jdufresne - Pass python\_requires argument to setuptools (#440) by @jdufresne - Rename [wheel] section to [bdist\_wheel] as the former is legacy (#441) by @jdufresne - Remove setup.py test command in favor of pytest and tox (#442) by @jdufresne - Fix mypy errors (#449) by @jpadilla - DX Tweaks (#450) by @jpadilla - Add support of python 3.8 (#452) by @Djailla - Fix 406 (#454) by @justinbaur - Add support for Ed25519 / EdDSA, with unit tests (#455) by @Someguy123 - Remove Python 2.7 compatibility (#457) by @Djailla - Fix simple typo: encododed -> encoded (#462) by @timgates42 - Enhance tracebacks. (#477) by @JulienPalard - Simplify ``python_requires`` (#478) by @michael-k - Document top-level .encode and .decode to close #459 (#482) by @dimaqq - Improve documentation for audience usage (#484) by @CorreyL - Correct README on how to run tests locally (#489) by @jdufresne - Fix ``tox -e lint`` warnings and errors (#490) by @jdufresne - Run pyupgrade across project to use modern Python 3 conventions (#491) by @jdufresne - Add Python-3-only trove classifier and remove "universal" from wheel (#492) by @jdufresne - Emit warnings about user code, not pyjwt code (#494) by @mgedmin - Move setup information to declarative setup.cfg (#495) by @jdufresne - CLI options for verifying audience and issuer (#496) by @GeoffRichards - Specify the target Python version for mypy (#497) by @jdufresne - Remove unnecessary compatibility shims for Python 2 (#498) by @jdufresne - Setup GH Actions (#499) by @jpadilla - Implementation of ECAlgorithm.from\_jwk (#500) by @jpadilla - Remove cli entry point (#501) by @jpadilla - Expose InvalidKeyError on jwt module (#503) by @russellcardullo - Avoid loading token twice in pyjwt.decode (#506) by @CaselIT - Default links to stable version of documentation (#508) by @salcedo - Update README.md badges (#510) by @jpadilla - Introduce better experience for JWKs (#511) by @jpadilla - Fix tox conditional extras (#512) by @jpadilla - Return tokens as string not bytes (#513) by @jpadilla - Drop support for legacy contrib algorithms (#514) by @jpadilla - Drop deprecation warnings (#515) by @jpadilla - Update Auth0 sponsorship link (#519) by @Sambego - Update return type for jwt.encode (#521) by @moomoolive - Run tests against Python 3.9 and add trove classifier (#522) by @michael-k - Removed redundant ``default_backend()`` (#523) by @rohitkg98 - Documents how to use private keys with passphrases (#525) by @rayluo - Update version to 2.0.0a1 (#528) by @jpadilla - Fix usage example (#530) by @nijel - add EdDSA to docs (#531) by @CircleOnCircles - Remove support for EOL Python 3.5 (#532) by @jdufresne - Upgrade to isort 5 and adjust configurations (#533) by @jdufresne - Remove unused argument "verify" from PyJWS.decode() (#534) by @jdufresne - Update typing syntax and usage for Python 3.6+ (#535) by @jdufresne - Run pyupgrade to simplify code and use Python 3.6 syntax (#536) by @jdufresne - Drop unknown pytest config option: strict (#537) by @jdufresne - Upgrade black version and usage (#538) by @jdufresne - Remove "Command line" sections from docs (#539) by @jdufresne - Use existing key\_path() utility function throughout tests (#540) by @jdufresne - Replace force\_bytes()/force\_unicode() in tests with literals (#541) by @jdufresne - Remove unnecessary Unicode decoding before json.loads() (#542) by @jdufresne - Remove unnecessary force\_bytes() calls priot to base64url\_decode() (#543) by @jdufresne - Remove deprecated arguments from docs (#544) by @jdufresne - Update code blocks in docs (#545) by @jdufresne - Refactor jwt/jwks\_client.py without requests dependency (#546) by @jdufresne - Tighten bytes/str boundaries and remove unnecessary coercing (#547) by @jdufresne - Replace codecs.open() with builtin open() (#548) by @jdufresne - Replace int\_from\_bytes() with builtin int.from\_bytes() (#549) by @jdufresne - Enforce .encode() return type using mypy (#551) by @jdufresne - Prefer direct indexing over options.get() (#552) by @jdufresne - Cleanup "noqa" comments (#553) by @jdufresne - Replace merge\_dict() with builtin dict unpacking generalizations (#555) by @jdufresne - Do not mutate the input payload in PyJWT.encode() (#557) by @jdufresne - Use direct indexing in PyJWKClient.get\_signing\_key\_from\_jwt() (#558) by @jdufresne - Split PyJWT/PyJWS classes to tighten type interfaces (#559) by @jdufresne - Simplify mocked\_response test utility function (#560) by @jdufresne - Autoupdate pre-commit hooks and apply them (#561) by @jdufresne - Remove unused argument "payload" from PyJWS.\ *verify*\ signature() (#562) by @jdufresne - Add utility functions to assist test skipping (#563) by @jdufresne - Type hint jwt.utils module (#564) by @jdufresne - Prefer ModuleNotFoundError over ImportError (#565) by @jdufresne - Fix tox "manifest" environment to pass (#566) by @jdufresne - Fix tox "docs" environment to pass (#567) by @jdufresne - Simplify black configuration to be closer to upstream defaults (#568) by @jdufresne - Use generator expressions (#569) by @jdufresne - Simplify from\_base64url\_uint() (#570) by @jdufresne - Drop lint environment from GitHub actions in favor of pre-commit.ci (#571) by @jdufresne - [pre-commit.ci] pre-commit autoupdate (#572) - Simplify tox configuration (#573) by @jdufresne - Combine identical test functions using pytest.mark.parametrize() (#574) by @jdufresne - Complete type hinting of jwks\_client.py (#578) by @jdufresne `v1.7.1 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Update test dependencies with pinned ranges - Fix pytest deprecation warnings `v1.7.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Remove CRLF line endings `#353 `__ Fixed ~~~~~ - Update usage.rst `#360 `__ Added ~~~~~ - Support for Python 3.7 `#375 `__ `#379 `__ `#384 `__ `v1.6.4 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Reverse an unintentional breaking API change to .decode() `#352 `__ `v1.6.3 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - All exceptions inherit from PyJWTError `#340 `__ Added ~~~~~ - Add type hints `#344 `__ - Add help module `7ca41e `__ Docs ~~~~ - Added section to usage docs for jwt.get\_unverified\_header() `#350 `__ - Update legacy instructions for using pycrypto `#337 `__ `v1.6.1 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Audience parameter throws ``InvalidAudienceError`` when application does not specify an audience, but the token does. `#336 `__ `v1.6.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Dropped support for python 2.6 and 3.3 `#301 `__ - An invalid signature now raises an ``InvalidSignatureError`` instead of ``DecodeError`` `#316 `__ Fixed ~~~~~ - Fix over-eager fallback to stdin `#304 `__ Added ~~~~~ - Audience parameter now supports iterables `#306 `__ `v1.5.3 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Increase required version of the cryptography package to >=1.4.0. Fixed ~~~~~ - Remove uses of deprecated functions from the cryptography package. - Warn about missing ``algorithms`` param to ``decode()`` only when ``verify`` param is ``True`` `#281 `__ `v1.5.2 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Ensure correct arguments order in decode super call `7c1e61d `__ `v1.5.1 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Change optparse for argparse. `#238 `__ Fixed ~~~~~ - Guard against PKCS1 PEM encoded public keys `#277 `__ - Add deprecation warning when decoding without specifying ``algorithms`` `#277 `__ - Improve deprecation messages `#270 `__ - PyJWT.decode: move verify param into options `#271 `__ Added ~~~~~ - Support for Python 3.6 `#262 `__ - Expose jwt.InvalidAlgorithmError `#264 `__ `v1.5.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Add support for ECDSA public keys in RFC 4253 (OpenSSH) format `#244 `__ - Renamed commandline script ``jwt`` to ``jwt-cli`` to avoid issues with the script clobbering the ``jwt`` module in some circumstances. `#187 `__ - Better error messages when using an algorithm that requires the cryptography package, but it isn't available `#230 `__ - Tokens with future 'iat' values are no longer rejected `#190 `__ - Non-numeric 'iat' values now raise InvalidIssuedAtError instead of DecodeError - Remove rejection of future 'iat' claims `#252 `__ Fixed ~~~~~ - Add back 'ES512' for backward compatibility (for now) `#225 `__ - Fix incorrectly named ECDSA algorithm `#219 `__ - Fix rpm build `#196 `__ Added ~~~~~ - Add JWK support for HMAC and RSA keys `#202 `__ `v1.4.2 `__ -------------------------------------------------------------------- Fixed ~~~~~ - A PEM-formatted key encoded as bytes could cause a ``TypeError`` to be raised `#213 `__ `v1.4.1 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Newer versions of Pytest could not detect warnings properly `#182 `__ - Non-string 'kid' value now raises ``InvalidTokenError`` `#174 `__ - ``jwt.decode(None)`` now gracefully fails with ``InvalidTokenError`` `#183 `__ `v1.4 `__ ------------------------------------------------------------------ Fixed ~~~~~ - Exclude Python cache files from PyPI releases. Added ~~~~~ - Added new options to require certain claims (require\_nbf, require\_iat, require\_exp) and raise ``MissingRequiredClaimError`` if they are not present. - If ``audience=`` or ``issuer=`` is specified but the claim is not present, ``MissingRequiredClaimError`` is now raised instead of ``InvalidAudienceError`` and ``InvalidIssuerError`` `v1.3 `__ ------------------------------------------------------------------ Fixed ~~~~~ - ECDSA (ES256, ES384, ES512) signatures are now being properly serialized `#158 `__ - RSA-PSS (PS256, PS384, PS512) signatures now use the proper salt length for PSS padding. `#163 `__ Added ~~~~~ - Added a new ``jwt.get_unverified_header()`` to parse and return the header portion of a token prior to signature verification. Removed ~~~~~~~ - Python 3.2 is no longer a supported platform. This version of Python is rarely used. Users affected by this should upgrade to 3.3+. `v1.2.0 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Added back ``verify_expiration=`` argument to ``jwt.decode()`` that was erroneously removed in `v1.1.0 `__. Changed ~~~~~~~ - Refactored JWS-specific logic out of PyJWT and into PyJWS superclass. `#141 `__ Deprecated ~~~~~~~~~~ - ``verify_expiration=`` argument to ``jwt.decode()`` is now deprecated and will be removed in a future version. Use the ``option=`` argument instead. `v1.1.0 `__ -------------------------------------------------------------------- Added ~~~~~ - Added support for PS256, PS384, and PS512 algorithms. `#132 `__ - Added flexible and complete verification options during decode. `#131 `__ - Added this CHANGELOG.md file. Deprecated ~~~~~~~~~~ - Deprecated usage of the .decode(..., verify=False) parameter. Fixed ~~~~~ - Fixed command line encoding. `#128 `__ `v1.0.1 `__ -------------------------------------------------------------------- Fixed ~~~~~ - Include jwt/contrib' and jwt/contrib/algorithms\` in setup.py so that they will actually be included when installing. `882524d `__ - Fix bin/jwt after removing jwt.header(). `bd57b02 `__ `v1.0.0 `__ -------------------------------------------------------------------- Changed ~~~~~~~ - Moved ``jwt.api.header`` out of the public API. `#85 `__ - Added README details how to extract public / private keys from an x509 certificate. `#100 `__ - Refactor api.py functions into an object (``PyJWT``). `#101 `__ - Added support for PyCrypto and ecdsa when cryptography isn't available. `#101 `__ Fixed ~~~~~ - Fixed a security vulnerability where ``alg=None`` header could bypass signature verification. `#109 `__ - Fixed a security vulnerability by adding support for a whitelist of allowed ``alg`` values ``jwt.decode(algorithms=[])``. `#110 `__ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1571613540.0 PyJWT-2.3.0/CODE_OF_CONDUCT.md0000644000076500000240000000631000000000000016174 0ustar00jpadillastaff00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@jpadilla.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html][version] [homepage]: https://www.contributor-covenant.org/ [version]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1543201457.0 PyJWT-2.3.0/LICENSE0000644000076500000240000000207000000000000014401 0ustar00jpadillastaff00000000000000The MIT License (MIT) Copyright (c) 2015 José Padilla Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1610420463.0 PyJWT-2.3.0/MANIFEST.in0000644000076500000240000000046500000000000015140 0ustar00jpadillastaff00000000000000include .pre-commit-config.yaml include CODE_OF_CONDUCT.md include AUTHORS.rst include CHANGELOG.rst include LICENSE include README.rst include tox.ini include jwt/py.typed graft docs graft tests exclude codecov.yml recursive-exclude docs/_build * recursive-exclude * *.py[co] recursive-exclude * __pycache__ ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1634399673.864155 PyJWT-2.3.0/PKG-INFO0000644000076500000240000000737300000000000014504 0ustar00jpadillastaff00000000000000Metadata-Version: 2.1 Name: PyJWT Version: 2.3.0 Summary: JSON Web Token implementation in Python Home-page: https://github.com/jpadilla/pyjwt Author: Jose Padilla Author-email: hello@jpadilla.com License: MIT Description: PyJWT ===== .. image:: https://github.com/jpadilla/pyjwt/workflows/CI/badge.svg :target: https://github.com/jpadilla/pyjwt/actions?query=workflow%3ACI .. image:: https://img.shields.io/pypi/v/pyjwt.svg :target: https://pypi.python.org/pypi/pyjwt .. image:: https://codecov.io/gh/jpadilla/pyjwt/branch/master/graph/badge.svg :target: https://codecov.io/gh/jpadilla/pyjwt .. image:: https://readthedocs.org/projects/pyjwt/badge/?version=stable :target: https://pyjwt.readthedocs.io/en/stable/ A Python implementation of `RFC 7519 `_. Original implementation was written by `@progrium `_. Sponsor ------- +--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/developers `_. | +--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png Installing ---------- Install with **pip**: .. code-block:: console $ pip install PyJWT Usage ----- .. code-block:: pycon >>> import jwt >>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") >>> print(encoded) eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U >>> jwt.decode(encoded, "secret", algorithms=["HS256"]) {'some': 'payload'} Documentation ------------- View the full docs online at https://pyjwt.readthedocs.io/en/stable/ Tests ----- You can run tests from the project root after cloning with: .. code-block:: console $ tox Keywords: json,jwt,security,signing,token,web Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Utilities Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: crypto Provides-Extra: tests Provides-Extra: docs Provides-Extra: dev ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1634399673.7951741 PyJWT-2.3.0/PyJWT.egg-info/0000755000076500000240000000000000000000000016044 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/PKG-INFO0000644000076500000240000000737300000000000017153 0ustar00jpadillastaff00000000000000Metadata-Version: 2.1 Name: PyJWT Version: 2.3.0 Summary: JSON Web Token implementation in Python Home-page: https://github.com/jpadilla/pyjwt Author: Jose Padilla Author-email: hello@jpadilla.com License: MIT Description: PyJWT ===== .. image:: https://github.com/jpadilla/pyjwt/workflows/CI/badge.svg :target: https://github.com/jpadilla/pyjwt/actions?query=workflow%3ACI .. image:: https://img.shields.io/pypi/v/pyjwt.svg :target: https://pypi.python.org/pypi/pyjwt .. image:: https://codecov.io/gh/jpadilla/pyjwt/branch/master/graph/badge.svg :target: https://codecov.io/gh/jpadilla/pyjwt .. image:: https://readthedocs.org/projects/pyjwt/badge/?version=stable :target: https://pyjwt.readthedocs.io/en/stable/ A Python implementation of `RFC 7519 `_. Original implementation was written by `@progrium `_. Sponsor ------- +--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/developers `_. | +--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png Installing ---------- Install with **pip**: .. code-block:: console $ pip install PyJWT Usage ----- .. code-block:: pycon >>> import jwt >>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") >>> print(encoded) eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U >>> jwt.decode(encoded, "secret", algorithms=["HS256"]) {'some': 'payload'} Documentation ------------- View the full docs online at https://pyjwt.readthedocs.io/en/stable/ Tests ----- You can run tests from the project root after cloning with: .. code-block:: console $ tox Keywords: json,jwt,security,signing,token,web Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Natural Language :: English Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Utilities Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: crypto Provides-Extra: tests Provides-Extra: docs Provides-Extra: dev ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/SOURCES.txt0000644000076500000240000000321100000000000017725 0ustar00jpadillastaff00000000000000.pre-commit-config.yaml AUTHORS.rst CHANGELOG.rst CODE_OF_CONDUCT.md LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini PyJWT.egg-info/PKG-INFO PyJWT.egg-info/SOURCES.txt PyJWT.egg-info/dependency_links.txt PyJWT.egg-info/not-zip-safe PyJWT.egg-info/requires.txt PyJWT.egg-info/top_level.txt docs/Makefile docs/algorithms.rst docs/api.rst docs/changelog.rst docs/conf.py docs/faq.rst docs/index.rst docs/installation.rst docs/requirements-docs.txt docs/usage.rst docs/_static/theme_overrides.css jwt/__init__.py jwt/algorithms.py jwt/api_jwk.py jwt/api_jws.py jwt/api_jwt.py jwt/exceptions.py jwt/help.py jwt/jwks_client.py jwt/py.typed jwt/utils.py tests/__init__.py tests/test_algorithms.py tests/test_api_jwk.py tests/test_api_jws.py tests/test_api_jwt.py tests/test_exceptions.py tests/test_jwks_client.py tests/test_jwt.py tests/test_utils.py tests/utils.py tests/keys/__init__.py tests/keys/jwk_ec_key_P-256.json tests/keys/jwk_ec_key_P-384.json tests/keys/jwk_ec_key_P-521.json tests/keys/jwk_ec_key_secp256k1.json tests/keys/jwk_ec_pub_P-256.json tests/keys/jwk_ec_pub_P-384.json tests/keys/jwk_ec_pub_P-521.json tests/keys/jwk_ec_pub_secp256k1.json tests/keys/jwk_hmac.json tests/keys/jwk_okp_key_Ed25519.json tests/keys/jwk_okp_key_Ed448.json tests/keys/jwk_okp_pub_Ed25519.json tests/keys/jwk_okp_pub_Ed448.json tests/keys/jwk_rsa_key.json tests/keys/jwk_rsa_pub.json tests/keys/testkey2_rsa.pub.pem tests/keys/testkey_ec.priv tests/keys/testkey_ec.pub tests/keys/testkey_ec_ssh.pub tests/keys/testkey_ed25519 tests/keys/testkey_ed25519.pub tests/keys/testkey_pkcs1.pub.pem tests/keys/testkey_rsa.cer tests/keys/testkey_rsa.priv tests/keys/testkey_rsa.pub././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/dependency_links.txt0000644000076500000240000000000100000000000022112 0ustar00jpadillastaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/not-zip-safe0000644000076500000240000000000100000000000020272 0ustar00jpadillastaff00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/requires.txt0000644000076500000240000000037600000000000020452 0ustar00jpadillastaff00000000000000 [crypto] cryptography>=3.3.1 [dev] sphinx sphinx-rtd-theme zope.interface cryptography>=3.3.1 pytest<7.0.0,>=6.0.0 coverage[toml]==5.0.4 mypy pre-commit [docs] sphinx sphinx-rtd-theme zope.interface [tests] pytest<7.0.0,>=6.0.0 coverage[toml]==5.0.4 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399673.0 PyJWT-2.3.0/PyJWT.egg-info/top_level.txt0000644000076500000240000000000400000000000020570 0ustar00jpadillastaff00000000000000jwt ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608569746.0 PyJWT-2.3.0/README.rst0000644000076500000240000000444000000000000015066 0ustar00jpadillastaff00000000000000PyJWT ===== .. image:: https://github.com/jpadilla/pyjwt/workflows/CI/badge.svg :target: https://github.com/jpadilla/pyjwt/actions?query=workflow%3ACI .. image:: https://img.shields.io/pypi/v/pyjwt.svg :target: https://pypi.python.org/pypi/pyjwt .. image:: https://codecov.io/gh/jpadilla/pyjwt/branch/master/graph/badge.svg :target: https://codecov.io/gh/jpadilla/pyjwt .. image:: https://readthedocs.org/projects/pyjwt/badge/?version=stable :target: https://pyjwt.readthedocs.io/en/stable/ A Python implementation of `RFC 7519 `_. Original implementation was written by `@progrium `_. Sponsor ------- +--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/developers `_. | +--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png Installing ---------- Install with **pip**: .. code-block:: console $ pip install PyJWT Usage ----- .. code-block:: pycon >>> import jwt >>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") >>> print(encoded) eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U >>> jwt.decode(encoded, "secret", algorithms=["HS256"]) {'some': 'payload'} Documentation ------------- View the full docs online at https://pyjwt.readthedocs.io/en/stable/ Tests ----- You can run tests from the project root after cloning with: .. code-block:: console $ tox ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1634399673.8094637 PyJWT-2.3.0/docs/0000755000076500000240000000000000000000000014325 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1543201457.0 PyJWT-2.3.0/docs/Makefile0000644000076500000240000001635500000000000015777 0ustar00jpadillastaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyJWT.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyJWT.qhc" applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyJWT" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyJWT" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1634399673.819029 PyJWT-2.3.0/docs/_static/0000755000076500000240000000000000000000000015753 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1543201457.0 PyJWT-2.3.0/docs/_static/theme_overrides.css0000644000076500000240000000055300000000000021654 0ustar00jpadillastaff00000000000000img.auth0-logo { max-width: 45px !important; } @media screen and (min-width: 767px) { .wy-table-responsive table td { /* !important prevents the common CSS stylesheets from overriding this as on RTD they are loaded after this stylesheet */ white-space: normal !important; } .wy-table-responsive { overflow: visible !important; } } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/docs/algorithms.rst0000644000076500000240000000672200000000000017237 0ustar00jpadillastaff00000000000000Digital Signature Algorithms ============================ The JWT specification supports several algorithms for cryptographic signing. This library currently supports: * HS256 - HMAC using SHA-256 hash algorithm (default) * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm * ES256 - ECDSA signature algorithm using SHA-256 hash algorithm * ES256K - ECDSA signature algorithm with secp256k1 curve using SHA-256 hash algorithm * ES384 - ECDSA signature algorithm using SHA-384 hash algorithm * ES512 - ECDSA signature algorithm using SHA-512 hash algorithm * RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm * RS384 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-384 hash algorithm * RS512 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-512 hash algorithm * PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256 * PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384 * PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512 * EdDSA - Both Ed25519 signature using SHA-512 and Ed448 signature using SHA-3 are supported. Ed25519 and Ed448 provide 128-bit and 224-bit security respectively. Asymmetric (Public-key) Algorithms ---------------------------------- Usage of RSA (RS\*) and EC (EC\*) algorithms require a basic understanding of how public-key cryptography is used with regards to digital signatures. If you are unfamiliar, you may want to read `this article `_. When using the RSASSA-PKCS1-v1_5 algorithms, the `key` argument in both ``jwt.encode()`` and ``jwt.decode()`` (``"secret"`` in the examples) is expected to be either an RSA public or private key in PEM or SSH format. The type of key (private or public) depends on whether you are signing or verifying a token. When using the ECDSA algorithms, the ``key`` argument is expected to be an Elliptic Curve public or private key in PEM format. The type of key (private or public) depends on whether you are signing or verifying. Specifying an Algorithm ----------------------- You can specify which algorithm you would like to use to sign the JWT by using the `algorithm` parameter: .. code-block:: pycon >>> encoded = jwt.encode({"some": "payload"}, "secret", algorithm="HS512") >>> print(encoded) eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.WTzLzFO079PduJiFIyzrOah54YaM8qoxH9fLMQoQhKtw3_fMGjImIOokijDkXVbyfBqhMo2GCNu4w9v7UXvnpA When decoding, you can also specify which algorithms you would like to permit when validating the JWT by using the `algorithms` parameter which takes a list of allowed algorithms: .. code-block:: pycon >>> jwt.decode(encoded, "secret", algorithms=["HS512", "HS256"]) {'some': 'payload'} In the above case, if the JWT has any value for its alg header other than HS512 or HS256, the claim will be rejected with an ``InvalidAlgorithmError``. .. warning:: Do **not** compute the ``algorithms`` parameter based on the ``alg`` from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities (see `RFC 8725 §2.1 `_). Instead, either hard-code a fixed value for ``algorithms``, or configure it in the same place you configure the ``key``. Make sure not to mix symmetric and asymmetric algorithms that interpret the ``key`` in different ways (e.g. HS\* and RS\*). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1628450912.0 PyJWT-2.3.0/docs/api.rst0000644000076500000240000001647100000000000015641 0ustar00jpadillastaff00000000000000API Reference ============= .. module:: jwt .. function:: encode(payload, key, algorithm="HS256", headers=None, json_encoder=None) Encode the ``payload`` as JSON Web Token. :param dict payload: JWT claims, e.g. ``dict(iss=..., aud=..., sub=...)`` :param str key: a key suitable for the chosen algorithm: * for **asymmetric algorithms**: PEM-formatted private key, a multiline string * for **symmetric algorithms**: plain string, sufficiently long for security :param str algorithm: algorithm to sign the token with, e.g. ``"ES256"``. If ``headers`` includes ``alg``, it will be preferred to this parameter. :param dict headers: additional JWT header fields, e.g. ``dict(kid="my-key-id")``. :param json.JSONEncoder json_encoder: custom JSON encoder for ``payload`` and ``headers`` :rtype: str :returns: a JSON Web Token .. function:: decode(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0) Verify the ``jwt`` token signature and return the token claims. :param str jwt: the token to be decoded :param str key: the key suitable for the allowed algorithm :param list algorithms: allowed algorithms, e.g. ``["ES256"]`` .. warning:: Do **not** compute the ``algorithms`` parameter based on the ``alg`` from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities (see `RFC 8725 §2.1 `_). Instead, either hard-code a fixed value for ``algorithms``, or configure it in the same place you configure the ``key``. Make sure not to mix symmetric and asymmetric algorithms that interpret the ``key`` in different ways (e.g. HS\* and RS\*). :param dict options: extended decoding and validation options * ``verify_signature=True`` verify the JWT cryptographic signature * ``require=[]`` list of claims that must be present. Example: ``require=["exp", "iat", "nbf"]``. **Only verifies that the claims exists**. Does not verify that the claims are valid. * ``verify_aud=verify_signature`` check that ``aud`` (audience) claim matches ``audience`` * ``verify_iss=verify_signature`` check that ``iss`` (issuer) claim matches ``issuer`` * ``verify_exp=verify_signature`` check that ``exp`` (expiration) claim value is in the future * ``verify_iat=verify_signature`` check that ``iat`` (issued at) claim value is an integer * ``verify_nbf=verify_signature`` check that ``nbf`` (not before) claim value is in the past .. warning:: ``exp``, ``iat`` and ``nbf`` will only be verified if present. Please pass respective value to ``require`` if you want to make sure that they are always present (and therefore always verified if ``verify_exp``, ``verify_iat``, and ``verify_nbf`` respectively is set to ``True``). :param Iterable audience: optional, the value for ``verify_aud`` check :param str issuer: optional, the value for ``verify_iss`` check :param float leeway: a time margin in seconds for the expiration check :rtype: dict :returns: the JWT claims .. function:: decode_complete(jwt, key="", algorithms=None, options=None, audience=None, issuer=None, leeway=0) Identical to ``jwt.decode`` except for return value which is a dictionary containing the token header (JOSE Header), the token payload (JWT Payload), and token signature (JWT Signature) on the keys "header", "payload", and "signature" respectively. :param str jwt: the token to be decoded :param str key: the key suitable for the allowed algorithm :param list algorithms: allowed algorithms, e.g. ``["ES256"]`` .. warning:: Do **not** compute the ``algorithms`` parameter based on the ``alg`` from the token itself, or on any other data that an attacker may be able to influence, as that might expose you to various vulnerabilities (see `RFC 8725 §2.1 `_). Instead, either hard-code a fixed value for ``algorithms``, or configure it in the same place you configure the ``key``. Make sure not to mix symmetric and asymmetric algorithms that interpret the ``key`` in different ways (e.g. HS\* and RS\*). :param dict options: extended decoding and validation options * ``verify_signature=True`` verify the JWT cryptographic signature * ``require=[]`` list of claims that must be present. Example: ``require=["exp", "iat", "nbf"]``. **Only verifies that the claims exists**. Does not verify that the claims are valid. * ``verify_aud=verify_signature`` check that ``aud`` (audience) claim matches ``audience`` * ``verify_iss=verify_signature`` check that ``iss`` (issuer) claim matches ``issuer`` * ``verify_exp=verify_signature`` check that ``exp`` (expiration) claim value is in the future * ``verify_iat=verify_signature`` check that ``iat`` (issued at) claim value is an integer * ``verify_nbf=verify_signature`` check that ``nbf`` (not before) claim value is in the past .. warning:: ``exp``, ``iat`` and ``nbf`` will only be verified if present. Please pass respective value to ``require`` if you want to make sure that they are always present (and therefore always verified if ``verify_exp``, ``verify_iat``, and ``verify_nbf`` respectively is set to ``True``). :param Iterable audience: optional, the value for ``verify_aud`` check :param str issuer: optional, the value for ``verify_iss`` check :param float leeway: a time margin in seconds for the expiration check :rtype: dict :returns: Decoded JWT with the JOSE Header on the key ``header``, the JWS Payload on the key ``payload``, and the JWS Signature on the key ``signature``. .. note:: TODO: Document PyJWS class Exceptions ---------- .. currentmodule:: jwt.exceptions .. class:: InvalidTokenError Base exception when ``decode()`` fails on a token .. class:: DecodeError Raised when a token cannot be decoded because it failed validation .. class:: InvalidSignatureError Raised when a token's signature doesn't match the one provided as part of the token. .. class:: ExpiredSignatureError Raised when a token's ``exp`` claim indicates that it has expired .. class:: InvalidAudienceError Raised when a token's ``aud`` claim does not match one of the expected audience values .. class:: InvalidIssuerError Raised when a token's ``iss`` claim does not match the expected issuer .. class:: InvalidIssuedAtError Raised when a token's ``iat`` claim is in the future .. class:: ImmatureSignatureError Raised when a token's ``nbf`` claim represents a time in the future .. class:: InvalidKeyError Raised when the specified key is not in the proper format .. class:: InvalidAlgorithmError Raised when the specified algorithm is not recognized by PyJWT .. class:: MissingRequiredClaimError Raised when a claim that is required to be present is not contained in the claimset ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1610420463.0 PyJWT-2.3.0/docs/changelog.rst0000644000076500000240000000003600000000000017005 0ustar00jpadillastaff00000000000000.. include:: ../CHANGELOG.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608569746.0 PyJWT-2.3.0/docs/conf.py0000644000076500000240000000750500000000000015633 0ustar00jpadillastaff00000000000000import os import re import sphinx_rtd_theme def read(*parts): """ Build an absolute path from *parts* and and return the contents of the resulting file. Assume UTF-8 encoding. """ here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, *parts), encoding="utf-8") as f: return f.read() def find_version(*file_paths): """ Build a path from *file_paths* and search for a ``__version__`` string inside. """ version_file = read(*file_paths) version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") # -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.todo", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "PyJWT" copyright = "2015, José Padilla" author = "José Padilla" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The full version, including alpha/beta/rc tags. release = find_version("../jwt/__init__.py") # The short X.Y version. version = release.rsplit(".", 1)[0] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # Intersphinx extension. intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), } # -- Options for HTML output ---------------------------------------------- html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] html_context = { "extra_css_files": [ # override wide tables in RTD theme "_static/theme_overrides.css" ] } # Output file base name for HTML help builder. htmlhelp_basename = "PyJWTdoc" # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "pyjwt", "PyJWT Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "PyJWT", "PyJWT Documentation", author, "PyJWT", "One line description of project.", "Miscellaneous", ) ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608422497.0 PyJWT-2.3.0/docs/faq.rst0000644000076500000240000000115500000000000015630 0ustar00jpadillastaff00000000000000Frequently Asked Questions ========================== How can I extract a public / private key from a x509 certificate? ----------------------------------------------------------------- The ``load_pem_x509_certificate()`` function from ``cryptography`` can be used to extract the public or private keys from a x509 certificate in PEM format. .. code-block:: python from cryptography.x509 import load_pem_x509_certificate cert_str = b"-----BEGIN CERTIFICATE-----MIIDETCCAfm..." cert_obj = load_pem_x509_certificate(cert_str) public_key = cert_obj.public_key() private_key = cert_obj.private_key() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1610420463.0 PyJWT-2.3.0/docs/index.rst0000644000076500000240000000374200000000000016174 0ustar00jpadillastaff00000000000000Welcome to ``PyJWT`` ==================== ``PyJWT`` is a Python library which allows you to encode and decode JSON Web Tokens (JWT). JWT is an open, industry-standard (`RFC 7519`_) for representing claims securely between two parties. Sponsor ------- +--------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |auth0-logo| | If you want to quickly add secure token-based authentication to Python projects, feel free to check Auth0's Python SDK and free plan at `auth0.com/developers `_. | +--------------+-----------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ .. |auth0-logo| image:: https://user-images.githubusercontent.com/83319/31722733-de95bbde-b3ea-11e7-96bf-4f4e8f915588.png Installation ------------ You can install ``pyjwt`` with ``pip``: .. code-block:: console $ pip install pyjwt See :doc:`Installation ` for more information. Example Usage ------------- .. doctest:: >>> import jwt >>> encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256") >>> print(encoded_jwt) eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzb21lIjoicGF5bG9hZCJ9.Joh1R2dYzkRvDkqv3sygm5YyK8Gi4ShZqbhK2gxcs2U >>> jwt.decode(encoded_jwt, "secret", algorithms=["HS256"]) {'some': 'payload'} See :doc:`Usage Examples ` for more examples. Index ----- .. toctree:: :maxdepth: 2 installation usage faq algorithms api changelog .. _`RFC 7519`: https://tools.ietf.org/html/rfc7519 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1628452524.0 PyJWT-2.3.0/docs/installation.rst0000644000076500000240000000142700000000000017564 0ustar00jpadillastaff00000000000000Installation ============ You can install ``PyJWT`` with ``pip``: .. code-block:: console $ pip install pyjwt .. _installation_cryptography: Cryptographic Dependencies (Optional) ------------------------------------- If you are planning on encoding or decoding tokens using certain digital signature algorithms (like RSA or ECDSA), you will need to install the cryptography_ library. This can be installed explicitly, or as a required extra in the ``pyjwt`` requirement: .. code-block:: console $ pip install pyjwt[crypto] The ``pyjwt[crypto]`` format is recommended in requirements files in projects using ``PyJWT``, as a separate ``cryptography`` requirement line may later be mistaken for an unused requirement and removed. .. _`cryptography`: https://cryptography.io ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1592576644.0 PyJWT-2.3.0/docs/requirements-docs.txt0000644000076500000240000000003000000000000020530 0ustar00jpadillastaff00000000000000sphinx sphinx_rtd_theme ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/docs/usage.rst0000644000076500000240000002644700000000000016200 0ustar00jpadillastaff00000000000000Usage Examples ============== Encoding & Decoding Tokens with HS256 ------------------------------------- .. code-block:: pycon >>> import jwt >>> key = "secret" >>> encoded = jwt.encode({"some": "payload"}, key, algorithm="HS256") >>> print(encoded) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg >>> jwt.decode(encoded, key, algorithms="HS256") {'some': 'payload'} Encoding & Decoding Tokens with RS256 (RSA) ------------------------------------------- RSA encoding and decoding require the ``cryptography`` module. See :ref:`installation_cryptography`. .. code-block:: pycon >>> import jwt >>> private_key = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..." >>> public_key = b"-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEAC..." >>> encoded = jwt.encode({"some": "payload"}, private_key, algorithm="RS256") >>> print(encoded) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg >>> decoded = jwt.decode(encoded, public_key, algorithms=["RS256"]) {'some': 'payload'} If your private key needs a passphrase, you need to pass in a ``PrivateKey`` object from ``cryptography``. .. code-block:: python from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend pem_bytes = b"-----BEGIN PRIVATE KEY-----\nMIGEAgEAMBAGByqGSM49AgEGBS..." passphrase = b"your password" private_key = serialization.load_pem_private_key( pem_bytes, password=passphrase, backend=default_backend() ) encoded = jwt.encode({"some": "payload"}, private_key, algorithm="RS256") Specifying Additional Headers ----------------------------- .. code-block:: pycon >>> jwt.encode( ... {"some": "payload"}, ... "secret", ... algorithm="HS256", ... headers={"kid": "230498151c214b788dd97f22b85410a5"}, ... ) 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjIzMDQ5ODE1MWMyMTRiNzg4ZGQ5N2YyMmI4NTQxMGE1In0.eyJzb21lIjoicGF5bG9hZCJ9.DogbDGmMHgA_bU05TAB-R6geQ2nMU2BRM-LnYEtefwg' Reading the Claimset without Validation --------------------------------------- If you wish to read the claimset of a JWT without performing validation of the signature or any of the registered claim names, you can set the ``verify_signature`` option to ``False``. Note: It is generally ill-advised to use this functionality unless you clearly understand what you are doing. Without digital signature information, the integrity or authenticity of the claimset cannot be trusted. .. code-block:: pycon >>> jwt.decode(encoded, options={"verify_signature": False}) {'some': 'payload'} Reading Headers without Validation ---------------------------------- Some APIs require you to read a JWT header without validation. For example, in situations where the token issuer uses multiple keys and you have no way of knowing in advance which one of the issuer's public keys or shared secrets to use for validation, the issuer may include an identifier for the key in the header. .. code-block:: pycon >>> jwt.get_unverified_header(encoded) {'alg': 'RS256', 'typ': 'JWT', 'kid': 'key-id-12345...'} Registered Claim Names ---------------------- The JWT specification defines some registered claim names and defines how they should be used. PyJWT supports these registered claim names: - "exp" (Expiration Time) Claim - "nbf" (Not Before Time) Claim - "iss" (Issuer) Claim - "aud" (Audience) Claim - "iat" (Issued At) Claim Expiration Time Claim (exp) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. You can pass the expiration time as a UTC UNIX timestamp (an int) or as a datetime, which will be converted into an int. For example: .. code-block:: python jwt.encode({"exp": 1371720939}, "secret") jwt.encode({"exp": datetime.now(tz=timezone.utc)}, "secret") Expiration time is automatically verified in `jwt.decode()` and raises `jwt.ExpiredSignatureError` if the expiration time is in the past: .. code-block:: python try: jwt.decode("JWT_STRING", "secret", algorithms=["HS256"]) except jwt.ExpiredSignatureError: # Signature has expired ... Expiration time will be compared to the current UTC time (as given by `timegm(datetime.now(tz=timezone.utc).utctimetuple())`), so be sure to use a UTC timestamp or datetime in encoding. You can turn off expiration time verification with the `verify_exp` parameter in the options argument. PyJWT also supports the leeway part of the expiration time definition, which means you can validate a expiration time which is in the past but not very far. For example, if you have a JWT payload with a expiration time set to 30 seconds after creation but you know that sometimes you will process it after 30 seconds, you can set a leeway of 10 seconds in order to have some margin: .. code-block:: python jwt_payload = jwt.encode( {"exp": datetime.datetime.now(tz=timezone.utc) + datetime.timedelta(seconds=30)}, "secret", ) time.sleep(32) # JWT payload is now expired # But with some leeway, it will still validate jwt.decode(jwt_payload, "secret", leeway=10, algorithms=["HS256"]) Instead of specifying the leeway as a number of seconds, a `datetime.timedelta` instance can be used. The last line in the example above is equivalent to: .. code-block:: python jwt.decode( jwt_payload, "secret", leeway=datetime.timedelta(seconds=10), algorithms=["HS256"] ) Not Before Time Claim (nbf) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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. The `nbf` claim works similarly to the `exp` claim above. .. code-block:: python jwt.encode({"nbf": 1371720939}, "secret") jwt.encode({"nbf": datetime.now(tz=timezone.utc)}, "secret") Issuer Claim (iss) ~~~~~~~~~~~~~~~~~~ The "iss" (issuer) claim identifies the principal that issued the JWT. The processing of this claim is generally application specific. The "iss" value is a case-sensitive string containing a StringOrURI value. Use of this claim is OPTIONAL. .. code-block:: python payload = {"some": "payload", "iss": "urn:foo"} token = jwt.encode(payload, "secret") decoded = jwt.decode(token, "secret", issuer="urn:foo", algorithms=["HS256"]) If the issuer claim is incorrect, `jwt.InvalidIssuerError` will be raised. Audience Claim (aud) ~~~~~~~~~~~~~~~~~~~~ The "aud" (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the "aud" claim when this claim is present, then the JWT MUST be rejected. In the general case, the "aud" value is an array of case- sensitive strings, each containing a StringOrURI value. .. code-block:: python payload = {"some": "payload", "aud": ["urn:foo", "urn:bar"]} token = jwt.encode(payload, "secret") decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"]) In the special case when the JWT has one audience, the "aud" value MAY be a single case-sensitive string containing a StringOrURI value. .. code-block:: python payload = {"some": "payload", "aud": "urn:foo"} token = jwt.encode(payload, "secret") decoded = jwt.decode(token, "secret", audience="urn:foo", algorithms=["HS256"]) If multiple audiences are accepted, the ``audience`` parameter for ``jwt.decode`` can also be an iterable .. code-block:: python payload = {"some": "payload", "aud": "urn:foo"} token = jwt.encode(payload, "secret") decoded = jwt.decode( token, "secret", audience=["urn:foo", "urn:bar"], algorithms=["HS256"] ) The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL. If the audience claim is incorrect, `jwt.InvalidAudienceError` will be raised. Issued At Claim (iat) ~~~~~~~~~~~~~~~~~~~~~ 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 the `iat` claim is not a number, an `jwt.InvalidIssuedAtError` exception will be raised. .. code-block:: python jwt.encode({"iat": 1371720939}, "secret") jwt.encode({"iat": datetime.now(tz=timezone.utc)}, "secret") Requiring Presence of Claims ---------------------------- If you wish to require one or more claims to be present in the claimset, you can set the ``require`` parameter to include these claims. .. code-block:: pycon >>> jwt.decode(encoded, options={"require": ["exp", "iss", "sub"]}) {'exp': 1371720939, 'iss': 'urn:foo', 'sub': '25c37522-f148-4cbf-8ee6-c4a9718dd0af'} Retrieve RSA signing keys from a JWKS endpoint ---------------------------------------------- .. code-block:: pycon >>> import jwt >>> from jwt import PyJWKClient >>> token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA" >>> kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" >>> url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" >>> jwks_client = PyJWKClient(url) >>> signing_key = jwks_client.get_signing_key_from_jwt(token) >>> data = jwt.decode( ... token, ... signing_key.key, ... algorithms=["RS256"], ... audience="https://expenses-api", ... options={"verify_exp": False}, ... ) >>> print(data) {'iss': 'https://dev-87evx9ru.auth0.com/', 'sub': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC@clients', 'aud': 'https://expenses-api', 'iat': 1572006954, 'exp': 1572006964, 'azp': 'aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC', 'gty': 'client-credentials'} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1634399673.8253305 PyJWT-2.3.0/jwt/0000755000076500000240000000000000000000000014201 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634399631.0 PyJWT-2.3.0/jwt/__init__.py0000644000076500000240000000302200000000000016307 0ustar00jpadillastaff00000000000000from .api_jwk import PyJWK, PyJWKSet from .api_jws import ( PyJWS, get_unverified_header, register_algorithm, unregister_algorithm, ) from .api_jwt import PyJWT, decode, encode from .exceptions import ( DecodeError, ExpiredSignatureError, ImmatureSignatureError, InvalidAlgorithmError, InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError, InvalidKeyError, InvalidSignatureError, InvalidTokenError, MissingRequiredClaimError, PyJWKClientError, PyJWKError, PyJWKSetError, PyJWTError, ) from .jwks_client import PyJWKClient __version__ = "2.3.0" __title__ = "PyJWT" __description__ = "JSON Web Token implementation in Python" __url__ = "https://pyjwt.readthedocs.io" __uri__ = __url__ __doc__ = __description__ + " <" + __uri__ + ">" __author__ = "José Padilla" __email__ = "hello@jpadilla.com" __license__ = "MIT" __copyright__ = "Copyright 2015-2020 José Padilla" __all__ = [ "PyJWS", "PyJWT", "PyJWKClient", "PyJWK", "PyJWKSet", "decode", "encode", "get_unverified_header", "register_algorithm", "unregister_algorithm", # Exceptions "DecodeError", "ExpiredSignatureError", "ImmatureSignatureError", "InvalidAlgorithmError", "InvalidAudienceError", "InvalidIssuedAtError", "InvalidIssuerError", "InvalidKeyError", "InvalidSignatureError", "InvalidTokenError", "MissingRequiredClaimError", "PyJWKClientError", "PyJWKError", "PyJWKSetError", "PyJWTError", ] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/jwt/algorithms.py0000644000076500000240000005302600000000000016732 0ustar00jpadillastaff00000000000000import hashlib import hmac import json from .exceptions import InvalidKeyError from .utils import ( base64url_decode, base64url_encode, der_to_raw_signature, force_bytes, from_base64url_uint, raw_to_der_signature, to_base64url_uint, ) try: import cryptography.exceptions from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec, padding from cryptography.hazmat.primitives.asymmetric.ec import ( EllipticCurvePrivateKey, EllipticCurvePublicKey, ) from cryptography.hazmat.primitives.asymmetric.ed448 import ( Ed448PrivateKey, Ed448PublicKey, ) from cryptography.hazmat.primitives.asymmetric.ed25519 import ( Ed25519PrivateKey, Ed25519PublicKey, ) from cryptography.hazmat.primitives.asymmetric.rsa import ( RSAPrivateKey, RSAPrivateNumbers, RSAPublicKey, RSAPublicNumbers, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp, rsa_recover_prime_factors, ) from cryptography.hazmat.primitives.serialization import ( Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key, load_pem_public_key, load_ssh_public_key, ) has_crypto = True except ModuleNotFoundError: has_crypto = False requires_cryptography = { "RS256", "RS384", "RS512", "ES256", "ES256K", "ES384", "ES521", "ES512", "PS256", "PS384", "PS512", "EdDSA", } def get_default_algorithms(): """ Returns the algorithms that are implemented by the library. """ default_algorithms = { "none": NoneAlgorithm(), "HS256": HMACAlgorithm(HMACAlgorithm.SHA256), "HS384": HMACAlgorithm(HMACAlgorithm.SHA384), "HS512": HMACAlgorithm(HMACAlgorithm.SHA512), } if has_crypto: default_algorithms.update( { "RS256": RSAAlgorithm(RSAAlgorithm.SHA256), "RS384": RSAAlgorithm(RSAAlgorithm.SHA384), "RS512": RSAAlgorithm(RSAAlgorithm.SHA512), "ES256": ECAlgorithm(ECAlgorithm.SHA256), "ES256K": ECAlgorithm(ECAlgorithm.SHA256), "ES384": ECAlgorithm(ECAlgorithm.SHA384), "ES521": ECAlgorithm(ECAlgorithm.SHA512), "ES512": ECAlgorithm( ECAlgorithm.SHA512 ), # Backward compat for #219 fix "PS256": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), "PS384": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), "PS512": RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512), "EdDSA": OKPAlgorithm(), } ) return default_algorithms class Algorithm: """ The interface for an algorithm used to sign and verify tokens. """ def prepare_key(self, key): """ Performs necessary validation and conversions on the key and returns the key value in the proper format for sign() and verify(). """ raise NotImplementedError def sign(self, msg, key): """ Returns a digital signature for the specified message using the specified key value. """ raise NotImplementedError def verify(self, msg, key, sig): """ Verifies that the specified digital signature is valid for the specified message and key values. """ raise NotImplementedError @staticmethod def to_jwk(key_obj): """ Serializes a given RSA key into a JWK """ raise NotImplementedError @staticmethod def from_jwk(jwk): """ Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object """ raise NotImplementedError class NoneAlgorithm(Algorithm): """ Placeholder for use when no signing or verification operations are required. """ def prepare_key(self, key): if key == "": key = None if key is not None: raise InvalidKeyError('When alg = "none", key value must be None.') return key def sign(self, msg, key): return b"" def verify(self, msg, key, sig): return False class HMACAlgorithm(Algorithm): """ Performs signing and verification operations using HMAC and the specified hash function. """ SHA256 = hashlib.sha256 SHA384 = hashlib.sha384 SHA512 = hashlib.sha512 def __init__(self, hash_alg): self.hash_alg = hash_alg def prepare_key(self, key): key = force_bytes(key) invalid_strings = [ b"-----BEGIN PUBLIC KEY-----", b"-----BEGIN CERTIFICATE-----", b"-----BEGIN RSA PUBLIC KEY-----", b"ssh-rsa", ] if any(string_value in key for string_value in invalid_strings): raise InvalidKeyError( "The specified key is an asymmetric key or x509 certificate and" " should not be used as an HMAC secret." ) return key @staticmethod def to_jwk(key_obj): return json.dumps( { "k": base64url_encode(force_bytes(key_obj)).decode(), "kty": "oct", } ) @staticmethod def from_jwk(jwk): try: if isinstance(jwk, str): obj = json.loads(jwk) elif isinstance(jwk, dict): obj = jwk else: raise ValueError except ValueError: raise InvalidKeyError("Key is not valid JSON") if obj.get("kty") != "oct": raise InvalidKeyError("Not an HMAC key") return base64url_decode(obj["k"]) def sign(self, msg, key): return hmac.new(key, msg, self.hash_alg).digest() def verify(self, msg, key, sig): return hmac.compare_digest(sig, self.sign(msg, key)) if has_crypto: class RSAAlgorithm(Algorithm): """ Performs signing and verification operations using RSASSA-PKCS-v1_5 and the specified hash function. """ SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, hash_alg): self.hash_alg = hash_alg def prepare_key(self, key): if isinstance(key, (RSAPrivateKey, RSAPublicKey)): return key if not isinstance(key, (bytes, str)): raise TypeError("Expecting a PEM-formatted key.") key = force_bytes(key) try: if key.startswith(b"ssh-rsa"): key = load_ssh_public_key(key) else: key = load_pem_private_key(key, password=None) except ValueError: key = load_pem_public_key(key) return key @staticmethod def to_jwk(key_obj): obj = None if getattr(key_obj, "private_numbers", None): # Private key numbers = key_obj.private_numbers() obj = { "kty": "RSA", "key_ops": ["sign"], "n": to_base64url_uint(numbers.public_numbers.n).decode(), "e": to_base64url_uint(numbers.public_numbers.e).decode(), "d": to_base64url_uint(numbers.d).decode(), "p": to_base64url_uint(numbers.p).decode(), "q": to_base64url_uint(numbers.q).decode(), "dp": to_base64url_uint(numbers.dmp1).decode(), "dq": to_base64url_uint(numbers.dmq1).decode(), "qi": to_base64url_uint(numbers.iqmp).decode(), } elif getattr(key_obj, "verify", None): # Public key numbers = key_obj.public_numbers() obj = { "kty": "RSA", "key_ops": ["verify"], "n": to_base64url_uint(numbers.n).decode(), "e": to_base64url_uint(numbers.e).decode(), } else: raise InvalidKeyError("Not a public or private key") return json.dumps(obj) @staticmethod def from_jwk(jwk): try: if isinstance(jwk, str): obj = json.loads(jwk) elif isinstance(jwk, dict): obj = jwk else: raise ValueError except ValueError: raise InvalidKeyError("Key is not valid JSON") if obj.get("kty") != "RSA": raise InvalidKeyError("Not an RSA key") if "d" in obj and "e" in obj and "n" in obj: # Private key if "oth" in obj: raise InvalidKeyError( "Unsupported RSA private key: > 2 primes not supported" ) other_props = ["p", "q", "dp", "dq", "qi"] props_found = [prop in obj for prop in other_props] any_props_found = any(props_found) if any_props_found and not all(props_found): raise InvalidKeyError( "RSA key must include all parameters if any are present besides d" ) public_numbers = RSAPublicNumbers( from_base64url_uint(obj["e"]), from_base64url_uint(obj["n"]), ) if any_props_found: numbers = RSAPrivateNumbers( d=from_base64url_uint(obj["d"]), p=from_base64url_uint(obj["p"]), q=from_base64url_uint(obj["q"]), dmp1=from_base64url_uint(obj["dp"]), dmq1=from_base64url_uint(obj["dq"]), iqmp=from_base64url_uint(obj["qi"]), public_numbers=public_numbers, ) else: d = from_base64url_uint(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() elif "n" in obj and "e" in obj: # Public key numbers = RSAPublicNumbers( from_base64url_uint(obj["e"]), from_base64url_uint(obj["n"]), ) return numbers.public_key() else: raise InvalidKeyError("Not a public or private key") def sign(self, msg, key): return key.sign(msg, padding.PKCS1v15(), self.hash_alg()) def verify(self, msg, key, sig): try: key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) return True except InvalidSignature: return False class ECAlgorithm(Algorithm): """ Performs signing and verification operations using ECDSA and the specified hash function """ SHA256 = hashes.SHA256 SHA384 = hashes.SHA384 SHA512 = hashes.SHA512 def __init__(self, hash_alg): self.hash_alg = hash_alg def prepare_key(self, key): if isinstance(key, (EllipticCurvePrivateKey, EllipticCurvePublicKey)): return key if not isinstance(key, (bytes, str)): raise TypeError("Expecting a PEM-formatted key.") key = force_bytes(key) # Attempt to load key. We don't know if it's # a Signing Key or a Verifying Key, so we try # the Verifying Key first. try: if key.startswith(b"ecdsa-sha2-"): key = load_ssh_public_key(key) else: key = load_pem_public_key(key) except ValueError: key = load_pem_private_key(key, password=None) return key def sign(self, msg, key): der_sig = key.sign(msg, ec.ECDSA(self.hash_alg())) return der_to_raw_signature(der_sig, key.curve) def verify(self, msg, key, sig): try: der_sig = raw_to_der_signature(sig, key.curve) except ValueError: return False try: if isinstance(key, EllipticCurvePrivateKey): key = key.public_key() key.verify(der_sig, msg, ec.ECDSA(self.hash_alg())) return True except InvalidSignature: return False @staticmethod def from_jwk(jwk): try: if isinstance(jwk, str): obj = json.loads(jwk) elif isinstance(jwk, dict): obj = jwk else: raise ValueError except ValueError: raise InvalidKeyError("Key is not valid JSON") if obj.get("kty") != "EC": raise InvalidKeyError("Not an Elliptic curve key") if "x" not in obj or "y" not in obj: raise InvalidKeyError("Not an Elliptic curve key") x = base64url_decode(obj.get("x")) y = base64url_decode(obj.get("y")) curve = obj.get("crv") if curve == "P-256": if len(x) == len(y) == 32: curve_obj = ec.SECP256R1() else: raise InvalidKeyError("Coords should be 32 bytes for curve P-256") elif curve == "P-384": if len(x) == len(y) == 48: curve_obj = ec.SECP384R1() else: raise InvalidKeyError("Coords should be 48 bytes for curve P-384") elif curve == "P-521": if len(x) == len(y) == 66: curve_obj = ec.SECP521R1() else: raise InvalidKeyError("Coords should be 66 bytes for curve P-521") elif curve == "secp256k1": if len(x) == len(y) == 32: curve_obj = ec.SECP256K1() else: raise InvalidKeyError( "Coords should be 32 bytes for curve secp256k1" ) else: raise InvalidKeyError(f"Invalid curve: {curve}") public_numbers = ec.EllipticCurvePublicNumbers( x=int.from_bytes(x, byteorder="big"), y=int.from_bytes(y, byteorder="big"), curve=curve_obj, ) if "d" not in obj: return public_numbers.public_key() d = base64url_decode(obj.get("d")) if len(d) != len(x): raise InvalidKeyError( "D should be {} bytes for curve {}", len(x), curve ) return ec.EllipticCurvePrivateNumbers( int.from_bytes(d, byteorder="big"), public_numbers ).private_key() class RSAPSSAlgorithm(RSAAlgorithm): """ Performs a signature using RSASSA-PSS with MGF1 """ def sign(self, msg, key): return key.sign( msg, padding.PSS( mgf=padding.MGF1(self.hash_alg()), salt_length=self.hash_alg.digest_size, ), self.hash_alg(), ) def verify(self, msg, key, sig): try: key.verify( sig, msg, padding.PSS( mgf=padding.MGF1(self.hash_alg()), salt_length=self.hash_alg.digest_size, ), self.hash_alg(), ) return True except InvalidSignature: return False class OKPAlgorithm(Algorithm): """ Performs signing and verification operations using EdDSA This class requires ``cryptography>=2.6`` to be installed. """ def __init__(self, **kwargs): pass def prepare_key(self, key): if isinstance( key, (Ed25519PrivateKey, Ed25519PublicKey, Ed448PrivateKey, Ed448PublicKey), ): return key if isinstance(key, (bytes, str)): if isinstance(key, str): key = key.encode("utf-8") str_key = key.decode("utf-8") if "-----BEGIN PUBLIC" in str_key: return load_pem_public_key(key) if "-----BEGIN PRIVATE" in str_key: return load_pem_private_key(key, password=None) if str_key[0:4] == "ssh-": return load_ssh_public_key(key) raise TypeError("Expecting a PEM-formatted or OpenSSH key.") def sign(self, msg, key): """ Sign a message ``msg`` using the EdDSA private key ``key`` :param str|bytes msg: Message to sign :param Ed25519PrivateKey}Ed448PrivateKey key: A :class:`.Ed25519PrivateKey` or :class:`.Ed448PrivateKey` iinstance :return bytes signature: The signature, as bytes """ msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg return key.sign(msg) def verify(self, msg, key, sig): """ Verify a given ``msg`` against a signature ``sig`` using the EdDSA key ``key`` :param str|bytes sig: EdDSA signature to check ``msg`` against :param str|bytes msg: Message to sign :param Ed25519PrivateKey|Ed25519PublicKey|Ed448PrivateKey|Ed448PublicKey key: A private or public EdDSA key instance :return bool verified: True if signature is valid, False if not. """ try: msg = bytes(msg, "utf-8") if type(msg) is not bytes else msg sig = bytes(sig, "utf-8") if type(sig) is not bytes else sig if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)): key = key.public_key() key.verify(sig, msg) return True # If no exception was raised, the signature is valid. except cryptography.exceptions.InvalidSignature: return False @staticmethod def to_jwk(key): if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): x = key.public_bytes( encoding=Encoding.Raw, format=PublicFormat.Raw, ) crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448" return json.dumps( { "x": base64url_encode(force_bytes(x)).decode(), "kty": "OKP", "crv": crv, } ) if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)): d = key.private_bytes( encoding=Encoding.Raw, format=PrivateFormat.Raw, encryption_algorithm=NoEncryption(), ) x = key.public_key().public_bytes( encoding=Encoding.Raw, format=PublicFormat.Raw, ) crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448" return json.dumps( { "x": base64url_encode(force_bytes(x)).decode(), "d": base64url_encode(force_bytes(d)).decode(), "kty": "OKP", "crv": crv, } ) raise InvalidKeyError("Not a public or private key") @staticmethod def from_jwk(jwk): try: if isinstance(jwk, str): obj = json.loads(jwk) elif isinstance(jwk, dict): obj = jwk else: raise ValueError except ValueError: raise InvalidKeyError("Key is not valid JSON") if obj.get("kty") != "OKP": raise InvalidKeyError("Not an Octet Key Pair") curve = obj.get("crv") if curve != "Ed25519" and curve != "Ed448": raise InvalidKeyError(f"Invalid curve: {curve}") if "x" not in obj: raise InvalidKeyError('OKP should have "x" parameter') x = base64url_decode(obj.get("x")) try: if "d" not in obj: if curve == "Ed25519": return Ed25519PublicKey.from_public_bytes(x) return Ed448PublicKey.from_public_bytes(x) d = base64url_decode(obj.get("d")) if curve == "Ed25519": return Ed25519PrivateKey.from_private_bytes(d) return Ed448PrivateKey.from_private_bytes(d) except ValueError as err: raise InvalidKeyError("Invalid key parameter") from err ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/jwt/api_jwk.py0000644000076500000240000000557600000000000016214 0ustar00jpadillastaff00000000000000import json from .algorithms import get_default_algorithms from .exceptions import InvalidKeyError, PyJWKError, PyJWKSetError class PyJWK: def __init__(self, jwk_data, algorithm=None): self._algorithms = get_default_algorithms() self._jwk_data = jwk_data kty = self._jwk_data.get("kty", None) if not kty: raise InvalidKeyError("kty is not found: %s" % self._jwk_data) if not algorithm and isinstance(self._jwk_data, dict): algorithm = self._jwk_data.get("alg", None) if not algorithm: # Determine alg with kty (and crv). crv = self._jwk_data.get("crv", None) if kty == "EC": if crv == "P-256" or not crv: algorithm = "ES256" elif crv == "P-384": algorithm = "ES384" elif crv == "P-521": algorithm = "ES512" elif crv == "secp256k1": algorithm = "ES256K" else: raise InvalidKeyError("Unsupported crv: %s" % crv) elif kty == "RSA": algorithm = "RS256" elif kty == "oct": algorithm = "HS256" elif kty == "OKP": if not crv: raise InvalidKeyError("crv is not found: %s" % self._jwk_data) if crv == "Ed25519": algorithm = "EdDSA" else: raise InvalidKeyError("Unsupported crv: %s" % crv) else: raise InvalidKeyError("Unsupported kty: %s" % kty) self.Algorithm = self._algorithms.get(algorithm) if not self.Algorithm: raise PyJWKError("Unable to find a algorithm for key: %s" % self._jwk_data) self.key = self.Algorithm.from_jwk(self._jwk_data) @staticmethod def from_dict(obj, algorithm=None): return PyJWK(obj, algorithm) @staticmethod def from_json(data, algorithm=None): obj = json.loads(data) return PyJWK.from_dict(obj, algorithm) @property def key_type(self): return self._jwk_data.get("kty", None) @property def key_id(self): return self._jwk_data.get("kid", None) @property def public_key_use(self): return self._jwk_data.get("use", None) class PyJWKSet: def __init__(self, keys): self.keys = [] if not keys or not isinstance(keys, list): raise PyJWKSetError("Invalid JWK Set value") if len(keys) == 0: raise PyJWKSetError("The JWK Set did not contain any keys") for key in keys: self.keys.append(PyJWK(key)) @staticmethod def from_dict(obj): keys = obj.get("keys", []) return PyJWKSet(keys) @staticmethod def from_json(data): obj = json.loads(data) return PyJWKSet.from_dict(obj) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634387019.0 PyJWT-2.3.0/jwt/api_jws.py0000644000076500000240000001756200000000000016222 0ustar00jpadillastaff00000000000000import binascii import json from collections.abc import Mapping from typing import Any, Dict, List, Optional, Type from .algorithms import ( Algorithm, get_default_algorithms, has_crypto, requires_cryptography, ) from .exceptions import ( DecodeError, InvalidAlgorithmError, InvalidSignatureError, InvalidTokenError, ) from .utils import base64url_decode, base64url_encode class PyJWS: header_typ = "JWT" def __init__(self, algorithms=None, options=None): self._algorithms = get_default_algorithms() self._valid_algs = ( set(algorithms) if algorithms is not None else set(self._algorithms) ) # Remove algorithms that aren't on the whitelist for key in list(self._algorithms.keys()): if key not in self._valid_algs: del self._algorithms[key] if options is None: options = {} self.options = {**self._get_default_options(), **options} @staticmethod def _get_default_options(): return {"verify_signature": True} def register_algorithm(self, alg_id, alg_obj): """ Registers a new Algorithm for use when creating and verifying tokens. """ if alg_id in self._algorithms: raise ValueError("Algorithm already has a handler.") if not isinstance(alg_obj, Algorithm): raise TypeError("Object is not of type `Algorithm`") self._algorithms[alg_id] = alg_obj self._valid_algs.add(alg_id) def unregister_algorithm(self, alg_id): """ Unregisters an Algorithm for use when creating and verifying tokens Throws KeyError if algorithm is not registered. """ if alg_id not in self._algorithms: raise KeyError( "The specified algorithm could not be removed" " because it is not registered." ) del self._algorithms[alg_id] self._valid_algs.remove(alg_id) def get_algorithms(self): """ Returns a list of supported values for the 'alg' parameter. """ return list(self._valid_algs) def encode( self, payload: bytes, key: str, algorithm: Optional[str] = "HS256", headers: Optional[Dict] = None, json_encoder: Optional[Type[json.JSONEncoder]] = None, ) -> str: segments = [] if algorithm is None: algorithm = "none" # Prefer headers["alg"] if present to algorithm parameter. if headers and "alg" in headers and headers["alg"]: algorithm = headers["alg"] # Header header = {"typ": self.header_typ, "alg": algorithm} if headers: self._validate_headers(headers) header.update(headers) if not header["typ"]: del header["typ"] json_header = json.dumps( header, separators=(",", ":"), cls=json_encoder ).encode() segments.append(base64url_encode(json_header)) segments.append(base64url_encode(payload)) # Segments signing_input = b".".join(segments) try: alg_obj = self._algorithms[algorithm] key = alg_obj.prepare_key(key) signature = alg_obj.sign(signing_input, key) except KeyError as e: if not has_crypto and algorithm in requires_cryptography: raise NotImplementedError( "Algorithm '%s' could not be found. Do you have cryptography " "installed?" % algorithm ) from e else: raise NotImplementedError("Algorithm not supported") from e segments.append(base64url_encode(signature)) encoded_string = b".".join(segments) return encoded_string.decode("utf-8") def decode_complete( self, jwt: str, key: str = "", algorithms: List[str] = None, options: Dict = None, **kwargs, ) -> Dict[str, Any]: if options is None: options = {} merged_options = {**self.options, **options} verify_signature = merged_options["verify_signature"] if verify_signature and not algorithms: raise DecodeError( 'It is required that you pass in a value for the "algorithms" argument when calling decode().' ) payload, signing_input, header, signature = self._load(jwt) if verify_signature: self._verify_signature(signing_input, header, signature, key, algorithms) return { "payload": payload, "header": header, "signature": signature, } def decode( self, jwt: str, key: str = "", algorithms: List[str] = None, options: Dict = None, **kwargs, ) -> str: decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) return decoded["payload"] def get_unverified_header(self, jwt): """Returns back the JWT header parameters as a dict() Note: The signature is not verified so the header parameters should not be fully trusted until signature verification is complete """ headers = self._load(jwt)[2] self._validate_headers(headers) return headers def _load(self, jwt): if isinstance(jwt, str): jwt = jwt.encode("utf-8") if not isinstance(jwt, bytes): raise DecodeError(f"Invalid token type. Token must be a {bytes}") try: signing_input, crypto_segment = jwt.rsplit(b".", 1) header_segment, payload_segment = signing_input.split(b".", 1) except ValueError as err: raise DecodeError("Not enough segments") from err try: header_data = base64url_decode(header_segment) except (TypeError, binascii.Error) as err: raise DecodeError("Invalid header padding") from err try: header = json.loads(header_data) except ValueError as e: raise DecodeError("Invalid header string: %s" % e) from e if not isinstance(header, Mapping): raise DecodeError("Invalid header string: must be a json object") try: payload = base64url_decode(payload_segment) except (TypeError, binascii.Error) as err: raise DecodeError("Invalid payload padding") from err try: signature = base64url_decode(crypto_segment) except (TypeError, binascii.Error) as err: raise DecodeError("Invalid crypto padding") from err return (payload, signing_input, header, signature) def _verify_signature( self, signing_input, header, signature, key="", algorithms=None, ): alg = header.get("alg") if algorithms is not None and alg not in algorithms: raise InvalidAlgorithmError("The specified alg value is not allowed") try: alg_obj = self._algorithms[alg] key = alg_obj.prepare_key(key) if not alg_obj.verify(signing_input, key, signature): raise InvalidSignatureError("Signature verification failed") except KeyError as e: raise InvalidAlgorithmError("Algorithm not supported") from e def _validate_headers(self, headers): if "kid" in headers: self._validate_kid(headers["kid"]) def _validate_kid(self, kid): if not isinstance(kid, str): raise InvalidTokenError("Key ID header parameter must be a string") _jws_global_obj = PyJWS() encode = _jws_global_obj.encode decode_complete = _jws_global_obj.decode_complete decode = _jws_global_obj.decode register_algorithm = _jws_global_obj.register_algorithm unregister_algorithm = _jws_global_obj.unregister_algorithm get_unverified_header = _jws_global_obj.get_unverified_header ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634387019.0 PyJWT-2.3.0/jwt/api_jwt.py0000644000076500000240000001623200000000000016214 0ustar00jpadillastaff00000000000000import json from calendar import timegm from collections.abc import Iterable, Mapping from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Type, Union from . import api_jws from .exceptions import ( DecodeError, ExpiredSignatureError, ImmatureSignatureError, InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError, MissingRequiredClaimError, ) class PyJWT: def __init__(self, options=None): if options is None: options = {} self.options = {**self._get_default_options(), **options} @staticmethod def _get_default_options() -> Dict[str, Union[bool, List[str]]]: return { "verify_signature": True, "verify_exp": True, "verify_nbf": True, "verify_iat": True, "verify_aud": True, "verify_iss": True, "require": [], } def encode( self, payload: Dict[str, Any], key: str, algorithm: Optional[str] = "HS256", headers: Optional[Dict] = None, json_encoder: Optional[Type[json.JSONEncoder]] = None, ) -> str: # Check that we get a mapping if not isinstance(payload, Mapping): raise TypeError( "Expecting a mapping object, as JWT only supports " "JSON objects as payloads." ) # Payload payload = payload.copy() for time_claim in ["exp", "iat", "nbf"]: # Convert datetime to a intDate value in known time-format claims if isinstance(payload.get(time_claim), datetime): payload[time_claim] = timegm(payload[time_claim].utctimetuple()) json_payload = json.dumps( payload, separators=(",", ":"), cls=json_encoder ).encode("utf-8") return api_jws.encode(json_payload, key, algorithm, headers, json_encoder) def decode_complete( self, jwt: str, key: str = "", algorithms: List[str] = None, options: Dict = None, **kwargs, ) -> Dict[str, Any]: if options is None: options = {"verify_signature": True} else: options.setdefault("verify_signature", True) if not options["verify_signature"]: options.setdefault("verify_exp", False) options.setdefault("verify_nbf", False) options.setdefault("verify_iat", False) options.setdefault("verify_aud", False) options.setdefault("verify_iss", False) if options["verify_signature"] and not algorithms: raise DecodeError( 'It is required that you pass in a value for the "algorithms" argument when calling decode().' ) decoded = api_jws.decode_complete( jwt, key=key, algorithms=algorithms, options=options, **kwargs, ) try: payload = json.loads(decoded["payload"]) except ValueError as e: raise DecodeError("Invalid payload string: %s" % e) if not isinstance(payload, dict): raise DecodeError("Invalid payload string: must be a json object") merged_options = {**self.options, **options} self._validate_claims(payload, merged_options, **kwargs) decoded["payload"] = payload return decoded def decode( self, jwt: str, key: str = "", algorithms: List[str] = None, options: Dict = None, **kwargs, ) -> Dict[str, Any]: decoded = self.decode_complete(jwt, key, algorithms, options, **kwargs) return decoded["payload"] def _validate_claims( self, payload, options, audience=None, issuer=None, leeway=0, **kwargs ): if isinstance(leeway, timedelta): leeway = leeway.total_seconds() if not isinstance(audience, (bytes, str, type(None), Iterable)): raise TypeError("audience must be a string, iterable, or None") self._validate_required_claims(payload, options) now = timegm(datetime.now(tz=timezone.utc).utctimetuple()) if "iat" in payload and options["verify_iat"]: self._validate_iat(payload, now, leeway) if "nbf" in payload and options["verify_nbf"]: self._validate_nbf(payload, now, leeway) if "exp" in payload and options["verify_exp"]: self._validate_exp(payload, now, leeway) if options["verify_iss"]: self._validate_iss(payload, issuer) if options["verify_aud"]: self._validate_aud(payload, audience) def _validate_required_claims(self, payload, options): for claim in options["require"]: if payload.get(claim) is None: raise MissingRequiredClaimError(claim) def _validate_iat(self, payload, now, leeway): try: int(payload["iat"]) except ValueError: raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.") def _validate_nbf(self, payload, now, leeway): try: nbf = int(payload["nbf"]) except ValueError: raise DecodeError("Not Before claim (nbf) must be an integer.") if nbf > (now + leeway): raise ImmatureSignatureError("The token is not yet valid (nbf)") def _validate_exp(self, payload, now, leeway): try: exp = int(payload["exp"]) except ValueError: raise DecodeError("Expiration Time claim (exp) must be an" " integer.") if exp < (now - leeway): raise ExpiredSignatureError("Signature has expired") def _validate_aud(self, payload, audience): if audience is None: if "aud" not in payload or not payload["aud"]: return # Application did not specify an audience, but # the token has the 'aud' claim raise InvalidAudienceError("Invalid audience") if "aud" not in payload or not payload["aud"]: # Application specified an audience, but it could not be # verified since the token does not contain a claim. raise MissingRequiredClaimError("aud") audience_claims = payload["aud"] if isinstance(audience_claims, str): audience_claims = [audience_claims] if not isinstance(audience_claims, list): raise InvalidAudienceError("Invalid claim format in token") if any(not isinstance(c, str) for c in audience_claims): raise InvalidAudienceError("Invalid claim format in token") if isinstance(audience, str): audience = [audience] if all(aud not in audience_claims for aud in audience): raise InvalidAudienceError("Invalid audience") def _validate_iss(self, payload, issuer): if issuer is None: return if "iss" not in payload: raise MissingRequiredClaimError("iss") if payload["iss"] != issuer: raise InvalidIssuerError("Invalid issuer") _jwt_global_obj = PyJWT() encode = _jwt_global_obj.encode decode_complete = _jwt_global_obj.decode_complete decode = _jwt_global_obj.decode ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/jwt/exceptions.py0000644000076500000240000000170500000000000016737 0ustar00jpadillastaff00000000000000class PyJWTError(Exception): """ Base class for all exceptions """ pass class InvalidTokenError(PyJWTError): pass class DecodeError(InvalidTokenError): pass class InvalidSignatureError(DecodeError): pass class ExpiredSignatureError(InvalidTokenError): pass class InvalidAudienceError(InvalidTokenError): pass class InvalidIssuerError(InvalidTokenError): pass class InvalidIssuedAtError(InvalidTokenError): pass class ImmatureSignatureError(InvalidTokenError): pass class InvalidKeyError(PyJWTError): pass class InvalidAlgorithmError(InvalidTokenError): pass class MissingRequiredClaimError(InvalidTokenError): def __init__(self, claim): self.claim = claim def __str__(self): return 'Token is missing the "%s" claim' % self.claim class PyJWKError(PyJWTError): pass class PyJWKSetError(PyJWTError): pass class PyJWKClientError(PyJWTError): pass ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608569746.0 PyJWT-2.3.0/jwt/help.py0000644000076500000240000000311000000000000015476 0ustar00jpadillastaff00000000000000import json import platform import sys from . import __version__ as pyjwt_version try: import cryptography except ModuleNotFoundError: cryptography = None # type: ignore def info(): """ Generate information for a bug report. Based on the requests package help utility module. """ try: platform_info = { "system": platform.system(), "release": platform.release(), } except OSError: platform_info = {"system": "Unknown", "release": "Unknown"} implementation = platform.python_implementation() if implementation == "CPython": implementation_version = platform.python_version() elif implementation == "PyPy": implementation_version = "{}.{}.{}".format( sys.pypy_version_info.major, sys.pypy_version_info.minor, sys.pypy_version_info.micro, ) if sys.pypy_version_info.releaselevel != "final": implementation_version = "".join( [implementation_version, sys.pypy_version_info.releaselevel] ) else: implementation_version = "Unknown" return { "platform": platform_info, "implementation": { "name": implementation, "version": implementation_version, }, "cryptography": {"version": getattr(cryptography, "__version__", "")}, "pyjwt": {"version": pyjwt_version}, } def main(): """Pretty-print the bug information as JSON.""" print(json.dumps(info(), sort_keys=True, indent=2)) if __name__ == "__main__": main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/jwt/jwks_client.py0000644000076500000240000000357200000000000017076 0ustar00jpadillastaff00000000000000import json import urllib.request from functools import lru_cache from typing import Any, List from .api_jwk import PyJWK, PyJWKSet from .api_jwt import decode_complete as decode_token from .exceptions import PyJWKClientError class PyJWKClient: def __init__(self, uri: str, cache_keys: bool = True, max_cached_keys: int = 16): self.uri = uri if cache_keys: # Cache signing keys # Ignore mypy (https://github.com/python/mypy/issues/2427) self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key) # type: ignore def fetch_data(self) -> Any: with urllib.request.urlopen(self.uri) as response: return json.load(response) def get_jwk_set(self) -> PyJWKSet: data = self.fetch_data() return PyJWKSet.from_dict(data) def get_signing_keys(self) -> List[PyJWK]: jwk_set = self.get_jwk_set() signing_keys = [ jwk_set_key for jwk_set_key in jwk_set.keys if jwk_set_key.public_key_use in ["sig", None] and jwk_set_key.key_id ] if not signing_keys: raise PyJWKClientError("The JWKS endpoint did not contain any signing keys") return signing_keys def get_signing_key(self, kid: str) -> PyJWK: signing_keys = self.get_signing_keys() signing_key = None for key in signing_keys: if key.key_id == kid: signing_key = key break if not signing_key: raise PyJWKClientError( f'Unable to find a signing key that matches: "{kid}"' ) return signing_key def get_signing_key_from_jwt(self, token: str) -> PyJWK: unverified = decode_token(token, options={"verify_signature": False}) header = unverified["header"] return self.get_signing_key(header.get("kid")) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608422497.0 PyJWT-2.3.0/jwt/py.typed0000644000076500000240000000000000000000000015666 0ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/jwt/utils.py0000644000076500000240000000464700000000000015726 0ustar00jpadillastaff00000000000000import base64 import binascii from typing import Any, Union try: from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve from cryptography.hazmat.primitives.asymmetric.utils import ( decode_dss_signature, encode_dss_signature, ) except ModuleNotFoundError: EllipticCurve = Any # type: ignore def force_bytes(value: Union[str, bytes]) -> bytes: if isinstance(value, str): return value.encode("utf-8") elif isinstance(value, bytes): return value else: raise TypeError("Expected a string value") def base64url_decode(input: Union[str, bytes]) -> bytes: if isinstance(input, str): input = input.encode("ascii") rem = len(input) % 4 if rem > 0: input += b"=" * (4 - rem) return base64.urlsafe_b64decode(input) def base64url_encode(input: bytes) -> bytes: return base64.urlsafe_b64encode(input).replace(b"=", b"") def to_base64url_uint(val: int) -> bytes: if val < 0: raise ValueError("Must be a positive integer") int_bytes = bytes_from_int(val) if len(int_bytes) == 0: int_bytes = b"\x00" return base64url_encode(int_bytes) def from_base64url_uint(val: Union[str, bytes]) -> int: if isinstance(val, str): val = val.encode("ascii") data = base64url_decode(val) return int.from_bytes(data, byteorder="big") def number_to_bytes(num: int, num_bytes: int) -> bytes: padded_hex = "%0*x" % (2 * num_bytes, num) return binascii.a2b_hex(padded_hex.encode("ascii")) def bytes_to_number(string: bytes) -> int: return int(binascii.b2a_hex(string), 16) def bytes_from_int(val: int) -> bytes: remaining = val byte_length = 0 while remaining != 0: remaining >>= 8 byte_length += 1 return val.to_bytes(byte_length, "big", signed=False) def der_to_raw_signature(der_sig: bytes, curve: EllipticCurve) -> bytes: num_bits = curve.key_size num_bytes = (num_bits + 7) // 8 r, s = decode_dss_signature(der_sig) return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes) def raw_to_der_signature(raw_sig: bytes, curve: EllipticCurve) -> bytes: num_bits = curve.key_size num_bytes = (num_bits + 7) // 8 if len(raw_sig) != 2 * num_bytes: raise ValueError("Invalid signature") r = bytes_to_number(raw_sig[:num_bytes]) s = bytes_to_number(raw_sig[num_bytes:]) return encode_dss_signature(r, s) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1634399673.865428 PyJWT-2.3.0/setup.cfg0000644000076500000240000000264700000000000015227 0ustar00jpadillastaff00000000000000[metadata] name = PyJWT version = attr: jwt.__version__ author = Jose Padilla author_email = hello@jpadilla.com description = JSON Web Token implementation in Python license = MIT keywords = json jwt security signing token web url = https://github.com/jpadilla/pyjwt long_description = file: README.rst long_description_content_type = text/x-rst classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers Natural Language :: English License :: OSI Approved :: MIT License Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Topic :: Utilities [options] zip_safe = false include_package_data = true python_requires = >=3.6 packages = find: [options.package_data] * = py.typed [options.extras_require] docs = sphinx sphinx-rtd-theme zope.interface crypto = cryptography>=3.3.1 tests = pytest>=6.0.0,<7.0.0 coverage[toml]==5.0.4 dev = sphinx sphinx-rtd-theme zope.interface cryptography>=3.3.1 pytest>=6.0.0,<7.0.0 coverage[toml]==5.0.4 mypy pre-commit [options.packages.find] exclude = tests tests.* [flake8] extend-ignore = E203, E501 [mypy] python_version = 3.6 ignore_missing_imports = true warn_unused_ignores = true [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1592579696.0 PyJWT-2.3.0/setup.py0000755000076500000240000000007600000000000015115 0ustar00jpadillastaff00000000000000#!/usr/bin/env python3 from setuptools import setup setup() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1634399673.8336656 PyJWT-2.3.0/tests/0000755000076500000240000000000000000000000014537 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1543201457.0 PyJWT-2.3.0/tests/__init__.py0000644000076500000240000000000000000000000016636 0ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1634399673.862809 PyJWT-2.3.0/tests/keys/0000755000076500000240000000000000000000000015512 5ustar00jpadillastaff00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608569746.0 PyJWT-2.3.0/tests/keys/__init__.py0000644000076500000240000000272000000000000017624 0ustar00jpadillastaff00000000000000import json import os from jwt.algorithms import has_crypto from jwt.utils import base64url_decode try: from cryptography.hazmat.primitives.asymmetric import ec except ModuleNotFoundError: pass if has_crypto: from jwt.algorithms import RSAAlgorithm BASE_PATH = os.path.dirname(os.path.abspath(__file__)) def decode_value(val): decoded = base64url_decode(val) return int.from_bytes(decoded, byteorder="big") def load_hmac_key(): with open(os.path.join(BASE_PATH, "jwk_hmac.json")) as infile: keyobj = json.load(infile) return base64url_decode(keyobj["k"]) def load_rsa_key(): with open(os.path.join(BASE_PATH, "jwk_rsa_key.json")) as infile: return RSAAlgorithm.from_jwk(infile.read()) def load_rsa_pub_key(): with open(os.path.join(BASE_PATH, "jwk_rsa_pub.json")) as infile: return RSAAlgorithm.from_jwk(infile.read()) def load_ec_key(): with open(os.path.join(BASE_PATH, "jwk_ec_key.json")) as infile: keyobj = json.load(infile) return ec.EllipticCurvePrivateNumbers( private_value=decode_value(keyobj["d"]), public_numbers=load_ec_pub_key_p_521().public_numbers(), ) def load_ec_pub_key_p_521(): with open(os.path.join(BASE_PATH, "jwk_ec_pub_P-521.json")) as infile: keyobj = json.load(infile) return ec.EllipticCurvePublicNumbers( x=decode_value(keyobj["x"]), y=decode_value(keyobj["y"]), curve=ec.SECP521R1(), ).public_key() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_ec_key_P-256.json0000644000076500000240000000036500000000000021314 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.256@hobbiton.example", "crv": "P-256", "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU", "d": "9GJquUJf57a9sev-u8-PoYlIezIPqI_vGpIaiu4zyZk" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/jwk_ec_key_P-384.json0000644000076500000240000000046400000000000021316 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.384@hobbiton.example", "crv": "P-384", "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", "y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy", "d": "xKPj5IXjiHpQpLOgyMGo6lg_DUp738SuXkiugCFMxbGNKTyTprYPfJz42wTOXbtd" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/jwk_ec_key_P-521.json0000644000076500000240000000057400000000000021311 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.521@hobbiton.example", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", "d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_ec_key_secp256k1.json0000644000076500000240000000037200000000000022224 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.256k@hobbiton.example", "crv": "secp256k1", "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI", "d": "XV7LOlEOANIaSxyil8yE8NPDT5jmVw_HQeCwNDzochQ" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_ec_pub_P-256.json0000644000076500000240000000027700000000000021314 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.256@hobbiton.example", "crv": "P-256", "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/jwk_ec_pub_P-384.json0000644000076500000240000000035100000000000021307 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.384@hobbiton.example", "crv": "P-384", "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", "y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/jwk_ec_pub_P-521.json0000644000076500000240000000043100000000000021277 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.521@hobbiton.example", "crv": "P-521", "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_ec_pub_secp256k1.json0000644000076500000240000000030400000000000022215 0ustar00jpadillastaff00000000000000{ "kty": "EC", "kid": "bilbo.baggins.256k@hobbiton.example", "crv": "secp256k1", "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/jwk_hmac.json0000664000076500000240000000025300000000000020172 0ustar00jpadillastaff00000000000000{ "kty": "oct", "kid": "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "use": "sig", "alg": "HS256", "k": "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_okp_key_Ed25519.json0000644000076500000240000000021700000000000021737 0ustar00jpadillastaff00000000000000{ "kty":"OKP", "crv":"Ed25519", "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/keys/jwk_okp_key_Ed448.json0000644000076500000240000000041600000000000021572 0ustar00jpadillastaff00000000000000{ "kty": "OKP", "kid": "sig_ed448_01", "crv": "Ed448", "use": "sig", "x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA", "d": "Zh5xx0r_0tq39xj-8jGuCwAA6wsDim2ME7cX_iXzqDRgPN8lsZZHu60AO7m31Fa4NtHO07eU63q8", "alg": "EdDSA" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1619609020.0 PyJWT-2.3.0/tests/keys/jwk_okp_pub_Ed25519.json0000644000076500000240000000013200000000000021731 0ustar00jpadillastaff00000000000000{ "kty":"OKP", "crv":"Ed25519", "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/keys/jwk_okp_pub_Ed448.json0000644000076500000240000000026700000000000021574 0ustar00jpadillastaff00000000000000{ "kty": "OKP", "kid": "sig_ed448_01", "crv": "Ed448", "use": "sig", "x": "kvqP7TzMosCQCpNcW8qY2HmVmpPYUEIGn-sQWQgoWlAZbWpnXpXqAT6yMoYA08pkJm7P_HKZoHwA", "alg": "EdDSA" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/jwk_rsa_key.json0000664000076500000240000000332100000000000020716 0ustar00jpadillastaff00000000000000{ "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" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/jwk_rsa_pub.json0000664000076500000240000000071500000000000020720 0ustar00jpadillastaff00000000000000{ "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" } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/testkey2_rsa.pub.pem0000664000076500000240000000070300000000000021423 0ustar00jpadillastaff00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1tUH3/0v8fvLensHO1g2 6+U4r7jBg43DVOgqmXAWQa8ArAb4NfTrsYX8YkVhZZYwuLmKczRj0GhXUVY9iDbT sIGmgG+ySj6eiREz5VLqofFkAvRZ6y7yNv8PIGgXEhQTiDDNIkHGaFNMvn/eZ54H is70pdTjR5Ko+/y/wg71df1nb/5KwttSvy0YsTu/XpkduonPruYfAVRG3HK+3GZd xTygLcdamwe9jj+kjxtXRlrXVMQiXGFSU8U6bjafWnQiQ9XzjxvygBt0ZD0kRorr p74XGyQY5ThkN8DlpJbTTFsxOnBUAQz4zhohjobIGBRimi5yVlyLOwTlpaKGFC7O 7wIDAQAB -----END PUBLIC KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/testkey_ec.priv0000644000076500000240000000036100000000000020553 0ustar00jpadillastaff00000000000000-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg2nninfu2jMHDwAbn 9oERUhRADS6duQaJEadybLaa0YShRANCAAQfMBxRZKUYEdy5/fLdGI2tYj6kTr50 PZPt8jOD23rAR7dhtNpG1ojqopmH0AH5wEXadgk8nLCT4cAPK59Qp9Ek -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/testkey_ec.pub0000644000076500000240000000026200000000000020361 0ustar00jpadillastaff00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHzAcUWSlGBHcuf3y3RiNrWI+pE6+ dD2T7fIzg9t6wEe3YbTaRtaI6qKZh9AB+cBF2nYJPJywk+HADyufUKfRJA== -----END PUBLIC KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/testkey_ec_ssh.pub0000644000076500000240000000024100000000000021233 0ustar00jpadillastaff00000000000000ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBB8wHFFkpRgR3Ln98t0Yja1iPqROvnQ9k+3yM4PbesBHt2G02kbWiOqimYfQAfnARdp2CTycsJPhwA8rn1Cn0SQ= ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598278508.0 PyJWT-2.3.0/tests/keys/testkey_ed255190000644000076500000240000000016700000000000020207 0ustar00jpadillastaff00000000000000-----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIBy9N4xfv/9qOiKrxwRKeGfO5ab6lSukKHbuC5vaJ1Mg -----END PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598278507.0 PyJWT-2.3.0/tests/keys/testkey_ed25519.pub0000644000076500000240000000012100000000000020762 0ustar00jpadillastaff00000000000000ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC4pK2dePGgctIAsh0H/tmUrLzx2Vc4Ltc8TN9nfuChG ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/testkey_pkcs1.pub.pem0000664000076500000240000000036700000000000021603 0ustar00jpadillastaff00000000000000-----BEGIN RSA PUBLIC KEY----- MIGHAoGBAOV/0Vl/5VdHcYpnILYzBGWo5JQVzo9wBkbxzjAStcAnTwvv1ZJTMXs6 fjz91f9hiMM4Z/5qNTE/EHlDWxVdj1pyRaQulZPUs0r9qJ02ogRRGLG3jjrzzbzF yj/pdNBwym0UJYC/Jmn/kMLwGiWI2nfa9vM5SovqZiAy2FD7eOtVAgED -----END RSA PUBLIC KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/testkey_rsa.cer0000664000076500000240000000240100000000000020541 0ustar00jpadillastaff00000000000000-----BEGIN CERTIFICATE----- MIIDhTCCAm2gAwIBAgIJANE4sir3EkX8MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMQ4wDAYDVQQK DAVQeUpXVDEZMBcGA1UECwwQVGVzdCBDZXJ0aWZpY2F0ZTAeFw0xNTAzMTgwMTE2 MTRaFw0xODAzMTcwMTE2MTRaMFkxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhh czEPMA0GA1UEBwwGQXVzdGluMQ4wDAYDVQQKDAVQeUpXVDEZMBcGA1UECwwQVGVz dCBDZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANR4 MwXyb9nDo0K8gsHvDRHpa4jkzRVimVIr3r1K0YZanJmSXQr7giUa/sQjfjpjvKsI CSUffH3jbo8VYPifS7N/1DgOB3BfZ2B+mqlVxCwBPB5PwC78YveprNQw7gL0BmmG fpQDcZb8XkBTmUm45M//ZofGi3hisKiS6d6fjoVAUKcLwFAD4PNvjlLYE1t50pY4 3ha9eAfKgJ3hknP8JdJ4vvtUkWVFxUqL83KkDpJWt1tu66y36w+i14I/07A7OLw9 T5yJtc3FXpyk+032CNe27Bvzv1nnMM9jZdfaS+4A6LDa7hd6ICVjatS8p/4oz0J5 Dy6WR8ob7osnGHCNw4kCAwEAAaNQME4wHQYDVR0OBBYEFDR6fVdFxZED6YMmD62W LlBW+qEBMB8GA1UdIwQYMBaAFDR6fVdFxZED6YMmD62WLlBW+qEBMAwGA1UdEwQF MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFwDNwm+lU/kGfWwiWM0Lv2aosXotoiG TsBSWIn2iYphq0vzlgChcNocN9zkaOz3zc9pcREP6lyqHpE0OEbNucHHDdU1L2he lLFOLOmkpP5fyPDXs9nKYhO8ygMByEonHm3K/VvCgrsSgJ3JuxMLUxnE55jQXGWV OqYQNo2J5h93Zd2HTTe19jCz+bbWnRBP5VvLAAAo5YSmk3iroWSPWAKkWOOecJ2Q /xnRyuWERsfvZiF/m9q7yDJ55LXVVm3Rufmy76SoTnJ2acap+XQNXBH/AxayeLUS OYmHWH61dUcsQtwXYHYRB8TTtMIwUCXGmthXkDJydEfrGcD0y6APIh8= -----END CERTIFICATE----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/keys/testkey_rsa.priv0000644000076500000240000000321700000000000020754 0ustar00jpadillastaff00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpQIBAAKCAQEA1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJd CvuCJRr+xCN+OmO8qwgJJR98feNujxVg+J9Ls3/UOA4HcF9nYH6aqVXELAE8Hk/A Lvxi96ms1DDuAvQGaYZ+lANxlvxeQFOZSbjkz/9mh8aLeGKwqJLp3p+OhUBQpwvA UAPg82+OUtgTW3nSljjeFr14B8qAneGSc/wl0ni++1SRZUXFSovzcqQOkla3W27r rLfrD6LXgj/TsDs4vD1PnIm1zcVenKT7TfYI17bsG/O/Wecwz2Nl19pL7gDosNru F3ogJWNq1Lyn/ijPQnkPLpZHyhvuiycYcI3DiQIDAQABAoIBAQCt9uzwBZ0HVGQs lGULnUu6SsC9iXlR9TVMTpdFrij4NODb7Tc5cs0QzJWkytrjvB4Se7XhK3KnMLyp cvu/Fc7J3fRJIVN98t+V5pOD6rGAxlIPD4Vv8z6lQcw8wQNgb6WAaZriXh93XJNf YBO2hSj0FU5CBZLUsxmqLQBIQ6RR/OUGAvThShouE9K4N0vKB2UPOCu5U+d5zS3W 44Q5uatxYiSHBTYIZDN4u27Nfo5WA+GTvFyeNsO6tNNWlYfRHSBtnm6SZDY/5i4J fxP2JY0waM81KRvuHTazY571lHM/TTvFDRUX5nvHIu7GToBKahfVLf26NJuTZYXR 5c09GAXBAoGBAO7a9M/dvS6eDhyESYyCjP6w61jD7UYJ1fudaYFrDeqnaQ857Pz4 BcKx3KMmLFiDvuMgnVVj8RToBGfMV0zP7sDnuFRJnWYcOeU8e2sWGbZmWGWzv0SD +AhppSZThU4mJ8aa/tgsepCHkJnfoX+3wN7S9NfGhM8GDGxTHJwBpxINAoGBAOO4 ZVtn9QEblmCX/Q5ejInl43Y9nRsfTy9lB9Lp1cyWCJ3eep6lzT60K3OZGVOuSgKQ vZ/aClMCMbqsAAG4fKBjREA6p7k4/qaMApHQum8APCh9WPsKLaavxko8ZDc41kZt hgKyUs2XOhW/BLjmzqwGryidvOfszDwhH7rNVmRtAoGBALYGdvrSaRHVsbtZtRM3 imuuOCx1Y6U0abZOx9Cw3PIukongAxLlkL5G/XX36WOrQxWkDUK930OnbXQM7ZrD +5dW/8p8L09Zw2VHKmb5eK7gYA1hZim4yJTgrdL/Y1+jBDz+cagcfWsXZMNfAZxr VLh628x0pVF/sof67pqVR9UhAoGBAMcQiLoQ9GJVhW1HMBYBnQVnCyJv1gjBo+0g emhrtVQ0y6+FrtdExVjNEzboXPWD5Hq9oKY+aswJnQM8HH1kkr16SU2EeN437pQU zKI/PtqN8AjNGp3JVgLioYp/pHOJofbLA10UGcJTMpmT9ELWsVA8P55X1a1AmYDu y9f2bFE5AoGAdjo95mB0LVYikNPa+NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXD X9slB8RA15uv56bmN04O//NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g xN+SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d/LO0Y2Kkc= -----END RSA PRIVATE KEY----- ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1586353983.0 PyJWT-2.3.0/tests/keys/testkey_rsa.pub0000664000076500000240000000062100000000000020560 0ustar00jpadillastaff00000000000000ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUeDMF8m/Zw6NCvILB7w0R6WuI5M0VYplSK969StGGWpyZkl0K+4IlGv7EI346Y7yrCAklH3x9426PFWD4n0uzf9Q4DgdwX2dgfpqpVcQsATweT8Au/GL3qazUMO4C9AZphn6UA3GW/F5AU5lJuOTP/2aHxot4YrCokunen46FQFCnC8BQA+Dzb45S2BNbedKWON4WvXgHyoCd4ZJz/CXSeL77VJFlRcVKi/NypA6SVrdbbuust+sPoteCP9OwOzi8PU+cibXNxV6cpPtN9gjXtuwb879Z5zDPY2XX2kvuAOiw2u4XeiAlY2rUvKf+KM9CeQ8ulkfKG+6LJxhwjcOJ aasmundo@mair.local ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/test_algorithms.py0000644000076500000240000010336300000000000020327 0ustar00jpadillastaff00000000000000import base64 import json import pytest from jwt.algorithms import Algorithm, HMACAlgorithm, NoneAlgorithm, has_crypto from jwt.exceptions import InvalidKeyError from jwt.utils import base64url_decode from .keys import load_ec_pub_key_p_521, load_hmac_key, load_rsa_pub_key from .utils import crypto_required, key_path if has_crypto: from jwt.algorithms import ECAlgorithm, OKPAlgorithm, RSAAlgorithm, RSAPSSAlgorithm class TestAlgorithms: def test_algorithm_should_throw_exception_if_prepare_key_not_impl(self): algo = Algorithm() with pytest.raises(NotImplementedError): algo.prepare_key("test") def test_algorithm_should_throw_exception_if_sign_not_impl(self): algo = Algorithm() with pytest.raises(NotImplementedError): algo.sign("message", "key") def test_algorithm_should_throw_exception_if_verify_not_impl(self): algo = Algorithm() with pytest.raises(NotImplementedError): algo.verify("message", "key", "signature") def test_algorithm_should_throw_exception_if_to_jwk_not_impl(self): algo = Algorithm() with pytest.raises(NotImplementedError): algo.from_jwk("value") def test_algorithm_should_throw_exception_if_from_jwk_not_impl(self): algo = Algorithm() with pytest.raises(NotImplementedError): algo.to_jwk("value") def test_none_algorithm_should_throw_exception_if_key_is_not_none(self): algo = NoneAlgorithm() with pytest.raises(InvalidKeyError): algo.prepare_key("123") def test_hmac_should_reject_nonstring_key(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) with pytest.raises(TypeError) as context: algo.prepare_key(object()) exception = context.value assert str(exception) == "Expected a string value" def test_hmac_should_accept_unicode_key(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) algo.prepare_key("awesome") @pytest.mark.parametrize( "key", [ "testkey2_rsa.pub.pem", "testkey2_rsa.pub.pem", "testkey_pkcs1.pub.pem", "testkey_rsa.cer", "testkey_rsa.pub", ], ) def test_hmac_should_throw_exception(self, key): algo = HMACAlgorithm(HMACAlgorithm.SHA256) with pytest.raises(InvalidKeyError): with open(key_path(key)) as keyfile: algo.prepare_key(keyfile.read()) def test_hmac_jwk_should_parse_and_verify(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) with open(key_path("jwk_hmac.json")) as keyfile: key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key, signature) def test_hmac_to_jwk_returns_correct_values(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) key = algo.to_jwk("secret") assert json.loads(key) == {"kty": "oct", "k": "c2VjcmV0"} def test_hmac_from_jwk_should_raise_exception_if_not_hmac_key(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: with pytest.raises(InvalidKeyError): algo.from_jwk(keyfile.read()) @crypto_required def test_rsa_should_parse_pem_public_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey2_rsa.pub.pem")) as pem_key: algo.prepare_key(pem_key.read()) @crypto_required def test_rsa_should_accept_pem_private_key_bytes(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.priv"), "rb") as pem_key: algo.prepare_key(pem_key.read()) @crypto_required def test_rsa_should_accept_unicode_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.priv")) as rsa_key: algo.prepare_key(rsa_key.read()) @crypto_required def test_rsa_should_reject_non_string_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with pytest.raises(TypeError): algo.prepare_key(None) @crypto_required def test_rsa_verify_should_return_false_if_signature_invalid(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) message = b"Hello World!" sig = base64.b64decode( b"yS6zk9DBkuGTtcBzLUzSpo9gGJxJFOGvUqN01iLhWHrzBQ9ZEz3+Ae38AXp" b"10RWwscp42ySC85Z6zoN67yGkLNWnfmCZSEv+xqELGEvBJvciOKsrhiObUl" b"2mveSc1oeO/2ujkGDkkkJ2epn0YliacVjZF5+/uDmImUfAAj8lzjnHlzYix" b"sn5jGz1H07jYYbi9diixN8IUhXeTafwFg02IcONhum29V40Wu6O5tAKWlJX" b"fHJnNUzAEUOXS0WahHVb57D30pcgIji9z923q90p5c7E2cU8V+E1qe8NdCA" b"APCDzZZ9zQ/dgcMVaBrGrgimrcLbPjueOKFgSO+SSjIElKA==" ) sig += b"123" # Signature is now invalid with open(key_path("testkey_rsa.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(message, pub_key, sig) assert not result @crypto_required def test_ec_jwk_public_and_private_keys_should_parse_and_verify(self): tests = { "P-256": ECAlgorithm.SHA256, "P-384": ECAlgorithm.SHA384, "P-521": ECAlgorithm.SHA512, "secp256k1": ECAlgorithm.SHA256, } for (curve, hash) in tests.items(): algo = ECAlgorithm(hash) with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) with open(key_path(f"jwk_ec_key_{curve}.json")) as keyfile: priv_key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", priv_key) assert algo.verify(b"Hello World!", pub_key, signature) @crypto_required def test_ec_jwk_fails_on_invalid_json(self): algo = ECAlgorithm(ECAlgorithm.SHA512) valid_points = { "P-256": { "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU", }, "P-384": { "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", "y": "eovmN9ocANS8IJxDAGSuC1FehTq5ZFLJU7XSPg36zHpv4H2byKGEcCBiwT4sFJsy", }, "P-521": { "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", }, "secp256k1": { "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI", }, } # Invalid JSON with pytest.raises(InvalidKeyError): algo.from_jwk("") # Bad key type with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "RSA"}') # Missing data with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "EC"}') with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "EC", "x": "1"}') with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "EC", "y": "1"}') # Missing curve with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "EC", "x": "dGVzdA==", "y": "dGVzdA=="}') # EC coordinates not equally long with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "EC", "x": "dGVzdHRlc3Q=", "y": "dGVzdA=="}') # EC coordinates length invalid for curve in ("P-256", "P-384", "P-521", "secp256k1"): with pytest.raises(InvalidKeyError): algo.from_jwk( '{{"kty": "EC", "crv": "{}", "x": "dGVzdA==", ' '"y": "dGVzdA=="}}'.format(curve) ) # EC private key length invalid for (curve, point) in valid_points.items(): with pytest.raises(InvalidKeyError): algo.from_jwk( '{{"kty": "EC", "crv": "{}", "x": "{}", "y": "{}", ' '"d": "dGVzdA=="}}'.format(curve, point["x"], point["y"]) ) @crypto_required def test_rsa_jwk_public_and_private_keys_should_parse_and_verify(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) with open(key_path("jwk_rsa_key.json")) as keyfile: priv_key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", priv_key) assert algo.verify(b"Hello World!", pub_key, signature) @crypto_required def test_rsa_private_key_to_jwk_works_with_from_jwk(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.priv")) as rsa_key: orig_key = algo.prepare_key(rsa_key.read()) parsed_key = algo.from_jwk(algo.to_jwk(orig_key)) assert parsed_key.private_numbers() == orig_key.private_numbers() assert ( parsed_key.private_numbers().public_numbers == orig_key.private_numbers().public_numbers ) @crypto_required def test_rsa_public_key_to_jwk_works_with_from_jwk(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.pub")) as rsa_key: orig_key = algo.prepare_key(rsa_key.read()) parsed_key = algo.from_jwk(algo.to_jwk(orig_key)) assert parsed_key.public_numbers() == orig_key.public_numbers() @crypto_required def test_rsa_jwk_private_key_with_other_primes_is_invalid(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_key.json")) as keyfile: with pytest.raises(InvalidKeyError): keydata = json.loads(keyfile.read()) keydata["oth"] = [] algo.from_jwk(json.dumps(keydata)) @crypto_required def test_rsa_jwk_private_key_with_missing_values_is_invalid(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_key.json")) as keyfile: with pytest.raises(InvalidKeyError): keydata = json.loads(keyfile.read()) del keydata["p"] algo.from_jwk(json.dumps(keydata)) @crypto_required def test_rsa_jwk_private_key_can_recover_prime_factors(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_key.json")) as keyfile: keybytes = keyfile.read() control_key = algo.from_jwk(keybytes).private_numbers() keydata = json.loads(keybytes) delete_these = ["p", "q", "dp", "dq", "qi"] for field in delete_these: del keydata[field] parsed_key = algo.from_jwk(json.dumps(keydata)).private_numbers() assert control_key.d == parsed_key.d assert control_key.p == parsed_key.p assert control_key.q == parsed_key.q assert control_key.dmp1 == parsed_key.dmp1 assert control_key.dmq1 == parsed_key.dmq1 assert control_key.iqmp == parsed_key.iqmp @crypto_required def test_rsa_jwk_private_key_with_missing_required_values_is_invalid(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_key.json")) as keyfile: with pytest.raises(InvalidKeyError): keydata = json.loads(keyfile.read()) del keydata["p"] algo.from_jwk(json.dumps(keydata)) @crypto_required def test_rsa_jwk_raises_exception_if_not_a_valid_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) # Invalid JSON with pytest.raises(InvalidKeyError): algo.from_jwk("{not-a-real-key") # Missing key parts with pytest.raises(InvalidKeyError): algo.from_jwk('{"kty": "RSA"}') @crypto_required def test_rsa_to_jwk_returns_correct_values_for_public_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) key = algo.to_jwk(pub_key) expected = { "e": "AQAB", "key_ops": ["verify"], "kty": "RSA", "n": ( "1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-" "OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms" "1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg" "82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r" "rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo" "sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ" ), } assert json.loads(key) == expected @crypto_required def test_rsa_to_jwk_returns_correct_values_for_private_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) key = algo.to_jwk(priv_key) expected = { "key_ops": ["sign"], "kty": "RSA", "e": "AQAB", "n": ( "1HgzBfJv2cOjQryCwe8NEelriOTNFWKZUivevUrRhlqcmZJdCvuCJRr-xCN-" "OmO8qwgJJR98feNujxVg-J9Ls3_UOA4HcF9nYH6aqVXELAE8Hk_ALvxi96ms" "1DDuAvQGaYZ-lANxlvxeQFOZSbjkz_9mh8aLeGKwqJLp3p-OhUBQpwvAUAPg" "82-OUtgTW3nSljjeFr14B8qAneGSc_wl0ni--1SRZUXFSovzcqQOkla3W27r" "rLfrD6LXgj_TsDs4vD1PnIm1zcVenKT7TfYI17bsG_O_Wecwz2Nl19pL7gDo" "sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ" ), "d": ( "rfbs8AWdB1RkLJRlC51LukrAvYl5UfU1TE6XRa4o-DTg2-03OXLNEMyVpMr" "a47weEnu14StypzC8qXL7vxXOyd30SSFTffLfleaTg-qxgMZSDw-Fb_M-pU" "HMPMEDYG-lgGma4l4fd1yTX2ATtoUo9BVOQgWS1LMZqi0ASEOkUfzlBgL04" "UoaLhPSuDdLygdlDzgruVPnec0t1uOEObmrcWIkhwU2CGQzeLtuzX6OVgPh" "k7xcnjbDurTTVpWH0R0gbZ5ukmQ2P-YuCX8T9iWNMGjPNSkb7h02s2Oe9ZR" "zP007xQ0VF-Z7xyLuxk6ASmoX1S39ujSbk2WF0eXNPRgFwQ" ), "q": ( "47hlW2f1ARuWYJf9Dl6MieXjdj2dGx9PL2UH0unVzJYInd56nqXNPrQrc5k" "ZU65KApC9n9oKUwIxuqwAAbh8oGNEQDqnuTj-powCkdC6bwA8KH1Y-wotpq" "_GSjxkNzjWRm2GArJSzZc6Fb8EuObOrAavKJ285-zMPCEfus1WZG0" ), "p": ( "7tr0z929Lp4OHIRJjIKM_rDrWMPtRgnV-51pgWsN6qdpDzns_PgFwrHcoyY" "sWIO-4yCdVWPxFOgEZ8xXTM_uwOe4VEmdZhw55Tx7axYZtmZYZbO_RIP4CG" "mlJlOFTiYnxpr-2Cx6kIeQmd-hf7fA3tL018aEzwYMbFMcnAGnEg0" ), "qi": ( "djo95mB0LVYikNPa-NgyDwLotLqrueb9IviMmn6zKHCwiOXReqXDX9slB8" "RA15uv56bmN04O__NyVFcgJ2ef169GZHiRFIgIy0Pl8LYkMhCYKKhyqM7g" "xN-SqGqDTKDC22j00S7jcvCaa1qadn1qbdfukZ4NXv7E2d_LO0Y2Kkc" ), "dp": ( "tgZ2-tJpEdWxu1m1EzeKa644LHVjpTRptk7H0LDc8i6SieADEuWQvkb9df" "fpY6tDFaQNQr3fQ6dtdAztmsP7l1b_ynwvT1nDZUcqZvl4ruBgDWFmKbjI" "lOCt0v9jX6MEPP5xqBx9axdkw18BnGtUuHrbzHSlUX-yh_rumpVH1SE" ), "dq": ( "xxCIuhD0YlWFbUcwFgGdBWcLIm_WCMGj7SB6aGu1VDTLr4Wu10TFWM0TNu" "hc9YPker2gpj5qzAmdAzwcfWSSvXpJTYR43jfulBTMoj8-2o3wCM0anclW" "AuKhin-kc4mh9ssDXRQZwlMymZP0QtaxUDw_nlfVrUCZgO7L1_ZsUTk" ), } assert json.loads(key) == expected @crypto_required def test_rsa_to_jwk_raises_exception_on_invalid_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with pytest.raises(InvalidKeyError): algo.to_jwk({"not": "a valid key"}) @crypto_required def test_rsa_from_jwk_raises_exception_on_invalid_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_hmac.json")) as keyfile: with pytest.raises(InvalidKeyError): algo.from_jwk(keyfile.read()) @crypto_required def test_ec_should_reject_non_string_key(self): algo = ECAlgorithm(ECAlgorithm.SHA256) with pytest.raises(TypeError): algo.prepare_key(None) @crypto_required def test_ec_should_accept_pem_private_key_bytes(self): algo = ECAlgorithm(ECAlgorithm.SHA256) with open(key_path("testkey_ec.priv"), "rb") as ec_key: algo.prepare_key(ec_key.read()) @crypto_required def test_ec_should_accept_ssh_public_key_bytes(self): algo = ECAlgorithm(ECAlgorithm.SHA256) with open(key_path("testkey_ec_ssh.pub")) as ec_key: algo.prepare_key(ec_key.read()) @crypto_required def test_ec_verify_should_return_false_if_signature_invalid(self): algo = ECAlgorithm(ECAlgorithm.SHA256) message = b"Hello World!" # Mess up the signature by replacing a known byte sig = base64.b64decode( b"AC+m4Jf/xI3guAC6w0w37t5zRpSCF6F4udEz5LiMiTIjCS4vcVe6dDOxK+M" b"mvkF8PxJuvqxP2CO3TR3okDPCl/NjATTO1jE+qBZ966CRQSSzcCM+tzcHzw" b"LZS5kbvKu0Acd/K6Ol2/W3B1NeV5F/gjvZn/jOwaLgWEUYsg0o4XVrAg65".replace( b"r", b"s" ) ) with open(key_path("testkey_ec.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(message, pub_key, sig) assert not result @crypto_required def test_ec_verify_should_return_false_if_signature_wrong_length(self): algo = ECAlgorithm(ECAlgorithm.SHA256) message = b"Hello World!" sig = base64.b64decode(b"AC+m4Jf/xI3guAC6w0w3") with open(key_path("testkey_ec.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(message, pub_key, sig) assert not result @crypto_required def test_rsa_pss_sign_then_verify_should_return_true(self): algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256) message = b"Hello World!" with open(key_path("testkey_rsa.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) sig = algo.sign(message, priv_key) with open(key_path("testkey_rsa.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(message, pub_key, sig) assert result @crypto_required def test_rsa_pss_verify_should_return_false_if_signature_invalid(self): algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256) jwt_message = b"Hello World!" jwt_sig = base64.b64decode( b"ywKAUGRIDC//6X+tjvZA96yEtMqpOrSppCNfYI7NKyon3P7doud5v65oWNu" b"vQsz0fzPGfF7mQFGo9Cm9Vn0nljm4G6PtqZRbz5fXNQBH9k10gq34AtM02c" b"/cveqACQ8gF3zxWh6qr9jVqIpeMEaEBIkvqG954E0HT9s9ybHShgHX9mlWk" b"186/LopP4xe5c/hxOQjwhv6yDlTiwJFiqjNCvj0GyBKsc4iECLGIIO+4mC4" b"daOCWqbpZDuLb1imKpmm8Nsm56kAxijMLZnpCcnPgyb7CqG+B93W9GHglA5" b"drUeR1gRtO7vqbZMsCAQ4bpjXxwbYyjQlEVuMl73UL6sOWg==" ) jwt_sig += b"123" # Signature is now invalid with open(key_path("testkey_rsa.pub")) as keyfile: jwt_pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) assert not result class TestAlgorithmsRFC7520: """ These test vectors were taken from RFC 7520 (https://tools.ietf.org/html/rfc7520) """ def test_hmac_verify_should_return_true_for_test_vector(self): """ This test verifies that HMAC verification works with a known good signature and key. Reference: https://tools.ietf.org/html/rfc7520#section-4.4 """ signing_input = ( b"eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LWVlZ" b"jMxNGJjNzAzNyJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ" b"29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIG" b"lmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmc" b"gd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4" ) signature = base64url_decode(b"s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0") algo = HMACAlgorithm(HMACAlgorithm.SHA256) key = algo.prepare_key(load_hmac_key()) result = algo.verify(signing_input, key, signature) assert result @crypto_required def test_rsa_verify_should_return_true_for_test_vector(self): """ This test verifies that RSA PKCS v1.5 verification works with a known good signature and key. Reference: https://tools.ietf.org/html/rfc7520#section-4.1 """ signing_input = ( b"eyJhbGciOiJSUzI1NiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb" b"XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb" b"3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS" b"Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU" b"geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4" ) signature = base64url_decode( b"MRjdkly7_-oTPTS3AXP41iQIGKa80A0ZmTuV5MEaHoxnW2e5CZ5NlKtainoFmKZop" b"dHM1O2U4mwzJdQx996ivp83xuglII7PNDi84wnB-BDkoBwA78185hX-Es4JIwmDLJ" b"K3lfWRa-XtL0RnltuYv746iYTh_qHRD68BNt1uSNCrUCTJDt5aAE6x8wW1Kt9eRo4" b"QPocSadnHXFxnt8Is9UzpERV0ePPQdLuW3IS_de3xyIrDaLGdjluPxUAhb6L2aXic" b"1U12podGU0KLUQSE_oI-ZnmKJ3F4uOZDnd6QZWJushZ41Axf_fcIe8u9ipH84ogor" b"ee7vjbU5y18kDquDg" ) algo = RSAAlgorithm(RSAAlgorithm.SHA256) key = algo.prepare_key(load_rsa_pub_key()) result = algo.verify(signing_input, key, signature) assert result @crypto_required def test_rsapss_verify_should_return_true_for_test_vector(self): """ This test verifies that RSA-PSS verification works with a known good signature and key. Reference: https://tools.ietf.org/html/rfc7520#section-4.2 """ signing_input = ( b"eyJhbGciOiJQUzM4NCIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb" b"XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb" b"3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS" b"Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU" b"geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4" ) signature = base64url_decode( b"cu22eBqkYDKgIlTpzDXGvaFfz6WGoz7fUDcfT0kkOy42miAh2qyBzk1xEsnk2IpN6" b"-tPid6VrklHkqsGqDqHCdP6O8TTB5dDDItllVo6_1OLPpcbUrhiUSMxbbXUvdvWXz" b"g-UD8biiReQFlfz28zGWVsdiNAUf8ZnyPEgVFn442ZdNqiVJRmBqrYRXe8P_ijQ7p" b"8Vdz0TTrxUeT3lm8d9shnr2lfJT8ImUjvAA2Xez2Mlp8cBE5awDzT0qI0n6uiP1aC" b"N_2_jLAeQTlqRHtfa64QQSUmFAAjVKPbByi7xho0uTOcbH510a6GYmJUAfmWjwZ6o" b"D4ifKo8DYM-X72Eaw" ) algo = RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384) key = algo.prepare_key(load_rsa_pub_key()) result = algo.verify(signing_input, key, signature) assert result @crypto_required def test_ec_verify_should_return_true_for_test_vector(self): """ This test verifies that ECDSA verification works with a known good signature and key. Reference: https://tools.ietf.org/html/rfc7520#section-4.3 """ signing_input = ( b"eyJhbGciOiJFUzUxMiIsImtpZCI6ImJpbGJvLmJhZ2dpbnNAaG9iYml0b24uZXhhb" b"XBsZSJ9.SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywgZ29pbmcgb" b"3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9hZCwgYW5kIGlmIHlvdS" b"Bkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXigJlzIG5vIGtub3dpbmcgd2hlcmU" b"geW91IG1pZ2h0IGJlIHN3ZXB0IG9mZiB0by4" ) signature = base64url_decode( b"AE_R_YZCChjn4791jSQCrdPZCNYqHXCTZH0-JZGYNlaAjP2kqaluUIIUnC9qvbu9P" b"lon7KRTzoNEuT4Va2cmL1eJAQy3mtPBu_u_sDDyYjnAMDxXPn7XrT0lw-kvAD890j" b"l8e2puQens_IEKBpHABlsbEPX6sFY8OcGDqoRuBomu9xQ2" ) algo = ECAlgorithm(ECAlgorithm.SHA512) key = algo.prepare_key(load_ec_pub_key_p_521()) result = algo.verify(signing_input, key, signature) assert result # private key can also be used. with open(key_path("jwk_ec_key_P-521.json")) as keyfile: private_key = algo.from_jwk(keyfile.read()) result = algo.verify(signing_input, private_key, signature) assert result @crypto_required class TestOKPAlgorithms: hello_world_sig = b"Qxa47mk/azzUgmY2StAOguAd4P7YBLpyCfU3JdbaiWnXM4o4WibXwmIHvNYgN3frtE2fcyd8OYEaOiD/KiwkCg==" hello_world = b"Hello World!" def test_okp_ed25519_should_reject_non_string_key(self): algo = OKPAlgorithm() with pytest.raises(TypeError): algo.prepare_key(None) with open(key_path("testkey_ed25519")) as keyfile: algo.prepare_key(keyfile.read()) with open(key_path("testkey_ed25519.pub")) as keyfile: algo.prepare_key(keyfile.read()) def test_okp_ed25519_should_accept_unicode_key(self): algo = OKPAlgorithm() with open(key_path("testkey_ed25519")) as ec_key: algo.prepare_key(ec_key.read()) def test_okp_ed25519_sign_should_generate_correct_signature_value(self): algo = OKPAlgorithm() jwt_message = self.hello_world expected_sig = base64.b64decode(self.hello_world_sig) with open(key_path("testkey_ed25519")) as keyfile: jwt_key = algo.prepare_key(keyfile.read()) with open(key_path("testkey_ed25519.pub")) as keyfile: jwt_pub_key = algo.prepare_key(keyfile.read()) algo.sign(jwt_message, jwt_key) result = algo.verify(jwt_message, jwt_pub_key, expected_sig) assert result def test_okp_ed25519_verify_should_return_false_if_signature_invalid(self): algo = OKPAlgorithm() jwt_message = self.hello_world jwt_sig = base64.b64decode(self.hello_world_sig) jwt_sig += b"123" # Signature is now invalid with open(key_path("testkey_ed25519.pub")) as keyfile: jwt_pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) assert not result def test_okp_ed25519_verify_should_return_true_if_signature_valid(self): algo = OKPAlgorithm() jwt_message = self.hello_world jwt_sig = base64.b64decode(self.hello_world_sig) with open(key_path("testkey_ed25519.pub")) as keyfile: jwt_pub_key = algo.prepare_key(keyfile.read()) result = algo.verify(jwt_message, jwt_pub_key, jwt_sig) assert result def test_okp_ed25519_prepare_key_should_be_idempotent(self): algo = OKPAlgorithm() with open(key_path("testkey_ed25519.pub")) as keyfile: jwt_pub_key_first = algo.prepare_key(keyfile.read()) jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) assert jwt_pub_key_first == jwt_pub_key_second def test_okp_ed25519_jwk_private_key_should_parse_and_verify(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key.public_key(), signature) def test_okp_ed25519_jwk_private_key_should_parse_and_verify_with_private_key_as_is( self, ): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key, signature) def test_okp_ed25519_jwk_public_key_should_parse_and_verify(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: priv_key = algo.from_jwk(keyfile.read()) with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", priv_key) assert algo.verify(b"Hello World!", pub_key, signature) def test_okp_ed25519_jwk_fails_on_invalid_json(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: valid_pub = json.loads(keyfile.read()) with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: valid_key = json.loads(keyfile.read()) # Invalid instance type with pytest.raises(InvalidKeyError): algo.from_jwk(123) # Invalid JSON with pytest.raises(InvalidKeyError): algo.from_jwk("") # Invalid kty, not "OKP" v = valid_pub.copy() v["kty"] = "oct" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid crv, not "Ed25519" v = valid_pub.copy() v["crv"] = "P-256" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid crv, "Ed448" v = valid_pub.copy() v["crv"] = "Ed448" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Missing x v = valid_pub.copy() del v["x"] with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid x v = valid_pub.copy() v["x"] = "123" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid d v = valid_key.copy() v["d"] = "123" with pytest.raises(InvalidKeyError): algo.from_jwk(v) def test_okp_ed25519_to_jwk_works_with_from_jwk(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: priv_key_1 = algo.from_jwk(keyfile.read()) with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: pub_key_1 = algo.from_jwk(keyfile.read()) pub = algo.to_jwk(pub_key_1) pub_key_2 = algo.from_jwk(pub) pri = algo.to_jwk(priv_key_1) priv_key_2 = algo.from_jwk(pri) signature_1 = algo.sign(b"Hello World!", priv_key_1) signature_2 = algo.sign(b"Hello World!", priv_key_2) assert algo.verify(b"Hello World!", pub_key_2, signature_1) assert algo.verify(b"Hello World!", pub_key_2, signature_2) def test_okp_to_jwk_raises_exception_on_invalid_key(self): algo = OKPAlgorithm() with pytest.raises(InvalidKeyError): algo.to_jwk({"not": "a valid key"}) def test_okp_ed448_jwk_private_key_should_parse_and_verify(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key.public_key(), signature) def test_okp_ed448_jwk_private_key_should_parse_and_verify_with_private_key_as_is( self, ): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key, signature) def test_okp_ed448_jwk_public_key_should_parse_and_verify(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: priv_key = algo.from_jwk(keyfile.read()) with open(key_path("jwk_okp_pub_Ed448.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) signature = algo.sign(b"Hello World!", priv_key) assert algo.verify(b"Hello World!", pub_key, signature) def test_okp_ed448_jwk_fails_on_invalid_json(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_pub_Ed448.json")) as keyfile: valid_pub = json.loads(keyfile.read()) with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: valid_key = json.loads(keyfile.read()) # Invalid instance type with pytest.raises(InvalidKeyError): algo.from_jwk(123) # Invalid JSON with pytest.raises(InvalidKeyError): algo.from_jwk("") # Invalid kty, not "OKP" v = valid_pub.copy() v["kty"] = "oct" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid crv, not "Ed448" v = valid_pub.copy() v["crv"] = "P-256" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid crv, "Ed25519" v = valid_pub.copy() v["crv"] = "Ed25519" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Missing x v = valid_pub.copy() del v["x"] with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid x v = valid_pub.copy() v["x"] = "123" with pytest.raises(InvalidKeyError): algo.from_jwk(v) # Invalid d v = valid_key.copy() v["d"] = "123" with pytest.raises(InvalidKeyError): algo.from_jwk(v) def test_okp_ed448_to_jwk_works_with_from_jwk(self): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: priv_key_1 = algo.from_jwk(keyfile.read()) with open(key_path("jwk_okp_pub_Ed448.json")) as keyfile: pub_key_1 = algo.from_jwk(keyfile.read()) pub = algo.to_jwk(pub_key_1) pub_key_2 = algo.from_jwk(pub) pri = algo.to_jwk(priv_key_1) priv_key_2 = algo.from_jwk(pri) signature_1 = algo.sign(b"Hello World!", priv_key_1) signature_2 = algo.sign(b"Hello World!", priv_key_2) assert algo.verify(b"Hello World!", pub_key_2, signature_1) assert algo.verify(b"Hello World!", pub_key_2, signature_2) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/test_api_jwk.py0000644000076500000240000001735500000000000017607 0ustar00jpadillastaff00000000000000import json import pytest from jwt.algorithms import has_crypto from jwt.api_jwk import PyJWK, PyJWKSet from jwt.exceptions import InvalidKeyError, PyJWKError from .utils import crypto_required, key_path if has_crypto: from jwt.algorithms import ECAlgorithm, HMACAlgorithm, OKPAlgorithm, RSAAlgorithm class TestPyJWK: @crypto_required def test_should_load_key_from_jwk_data_dict(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) key_data_str = algo.to_jwk(pub_key) key_data = json.loads(key_data_str) # TODO Should `to_jwk` set these? key_data["alg"] = "RS256" key_data["use"] = "sig" key_data["kid"] = "keyid-abc123" jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "RSA" assert jwk.key_id == "keyid-abc123" assert jwk.public_key_use == "sig" @crypto_required def test_should_load_key_from_jwk_data_json_string(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) key_data_str = algo.to_jwk(pub_key) key_data = json.loads(key_data_str) # TODO Should `to_jwk` set these? key_data["alg"] = "RS256" key_data["use"] = "sig" key_data["kid"] = "keyid-abc123" jwk = PyJWK.from_json(json.dumps(key_data)) assert jwk.key_type == "RSA" assert jwk.key_id == "keyid-abc123" assert jwk.public_key_use == "sig" @crypto_required def test_should_load_key_without_alg_from_dict(self): with open(key_path("jwk_rsa_pub.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "RSA" assert isinstance(jwk.Algorithm, RSAAlgorithm) assert jwk.Algorithm.hash_alg == RSAAlgorithm.SHA256 @crypto_required def test_should_load_key_from_dict_with_algorithm(self): with open(key_path("jwk_rsa_pub.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data, algorithm="RS256") assert jwk.key_type == "RSA" assert isinstance(jwk.Algorithm, RSAAlgorithm) assert jwk.Algorithm.hash_alg == RSAAlgorithm.SHA256 @crypto_required def test_should_load_key_ec_p256_from_dict(self): with open(key_path("jwk_ec_pub_P-256.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "EC" assert isinstance(jwk.Algorithm, ECAlgorithm) assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA256 @crypto_required def test_should_load_key_ec_p384_from_dict(self): with open(key_path("jwk_ec_pub_P-384.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "EC" assert isinstance(jwk.Algorithm, ECAlgorithm) assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA384 @crypto_required def test_should_load_key_ec_p521_from_dict(self): with open(key_path("jwk_ec_pub_P-521.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "EC" assert isinstance(jwk.Algorithm, ECAlgorithm) assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA512 @crypto_required def test_should_load_key_ec_secp256k1_from_dict(self): with open(key_path("jwk_ec_pub_secp256k1.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "EC" assert isinstance(jwk.Algorithm, ECAlgorithm) assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA256 @crypto_required def test_should_load_key_hmac_from_dict(self): with open(key_path("jwk_hmac.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "oct" assert isinstance(jwk.Algorithm, HMACAlgorithm) assert jwk.Algorithm.hash_alg == HMACAlgorithm.SHA256 @crypto_required def test_should_load_key_hmac_without_alg_from_dict(self): with open(key_path("jwk_hmac.json")) as keyfile: key_data = json.loads(keyfile.read()) del key_data["alg"] jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "oct" assert isinstance(jwk.Algorithm, HMACAlgorithm) assert jwk.Algorithm.hash_alg == HMACAlgorithm.SHA256 @crypto_required def test_should_load_key_okp_without_alg_from_dict(self): with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: key_data = json.loads(keyfile.read()) jwk = PyJWK.from_dict(key_data) assert jwk.key_type == "OKP" assert isinstance(jwk.Algorithm, OKPAlgorithm) @crypto_required def test_from_dict_should_throw_exception_if_arg_is_invalid(self): with open(key_path("jwk_rsa_pub.json")) as keyfile: valid_rsa_pub = json.loads(keyfile.read()) with open(key_path("jwk_ec_pub_P-256.json")) as keyfile: valid_ec_pub = json.loads(keyfile.read()) with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: valid_okp_pub = json.loads(keyfile.read()) # Unknown algorithm with pytest.raises(PyJWKError): PyJWK.from_dict(valid_rsa_pub, algorithm="unknown") # Missing kty v = valid_rsa_pub.copy() del v["kty"] with pytest.raises(InvalidKeyError): PyJWK.from_dict(v) # Unknown kty v = valid_rsa_pub.copy() v["kty"] = "unknown" with pytest.raises(InvalidKeyError): PyJWK.from_dict(v) # Unknown EC crv v = valid_ec_pub.copy() v["crv"] = "unknown" with pytest.raises(InvalidKeyError): PyJWK.from_dict(v) # Unknown OKP crv v = valid_okp_pub.copy() v["crv"] = "unknown" with pytest.raises(InvalidKeyError): PyJWK.from_dict(v) # Missing OKP crv v = valid_okp_pub.copy() del v["crv"] with pytest.raises(InvalidKeyError): PyJWK.from_dict(v) class TestPyJWKSet: @crypto_required def test_should_load_keys_from_jwk_data_dict(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) key_data_str = algo.to_jwk(pub_key) key_data = json.loads(key_data_str) # TODO Should `to_jwk` set these? key_data["alg"] = "RS256" key_data["use"] = "sig" key_data["kid"] = "keyid-abc123" jwk_set = PyJWKSet.from_dict({"keys": [key_data]}) jwk = jwk_set.keys[0] assert jwk.key_type == "RSA" assert jwk.key_id == "keyid-abc123" assert jwk.public_key_use == "sig" @crypto_required def test_should_load_keys_from_jwk_data_json_string(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("jwk_rsa_pub.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) key_data_str = algo.to_jwk(pub_key) key_data = json.loads(key_data_str) # TODO Should `to_jwk` set these? key_data["alg"] = "RS256" key_data["use"] = "sig" key_data["kid"] = "keyid-abc123" jwk_set = PyJWKSet.from_json(json.dumps({"keys": [key_data]})) jwk = jwk_set.keys[0] assert jwk.key_type == "RSA" assert jwk.key_id == "keyid-abc123" assert jwk.public_key_use == "sig" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1628450912.0 PyJWT-2.3.0/tests/test_api_jws.py0000644000076500000240000006272100000000000017614 0ustar00jpadillastaff00000000000000import json from decimal import Decimal import pytest from jwt.algorithms import Algorithm, has_crypto from jwt.api_jws import PyJWS from jwt.exceptions import ( DecodeError, InvalidAlgorithmError, InvalidSignatureError, InvalidTokenError, ) from jwt.utils import base64url_decode from .utils import crypto_required, key_path, no_crypto_required try: from cryptography.hazmat.primitives.serialization import ( load_pem_private_key, load_pem_public_key, load_ssh_public_key, ) except ModuleNotFoundError: pass @pytest.fixture def jws(): return PyJWS() @pytest.fixture def payload(): """Creates a sample jws claimset for use as a payload during tests""" return b"hello world" class TestJWS: def test_register_algo_does_not_allow_duplicate_registration(self, jws): jws.register_algorithm("AAA", Algorithm()) with pytest.raises(ValueError): jws.register_algorithm("AAA", Algorithm()) def test_register_algo_rejects_non_algorithm_obj(self, jws): with pytest.raises(TypeError): jws.register_algorithm("AAA123", {}) def test_unregister_algo_removes_algorithm(self, jws): supported = jws.get_algorithms() assert "none" in supported assert "HS256" in supported jws.unregister_algorithm("HS256") supported = jws.get_algorithms() assert "HS256" not in supported def test_unregister_algo_throws_error_if_not_registered(self, jws): with pytest.raises(KeyError): jws.unregister_algorithm("AAA") def test_algo_parameter_removes_alg_from_algorithms_list(self, jws): assert "none" in jws.get_algorithms() assert "HS256" in jws.get_algorithms() jws = PyJWS(algorithms=["HS256"]) assert "none" not in jws.get_algorithms() assert "HS256" in jws.get_algorithms() def test_override_options(self): jws = PyJWS(options={"verify_signature": False}) assert not jws.options["verify_signature"] def test_non_object_options_dont_persist(self, jws, payload): token = jws.encode(payload, "secret") jws.decode(token, "secret", options={"verify_signature": False}) assert jws.options["verify_signature"] def test_options_must_be_dict(self, jws): pytest.raises(TypeError, PyJWS, options=object()) pytest.raises(TypeError, PyJWS, options=("something")) def test_encode_decode(self, jws, payload): secret = "secret" jws_message = jws.encode(payload, secret, algorithm="HS256") decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"]) assert decoded_payload == payload def test_decode_fails_when_alg_is_not_on_method_algorithms_param( self, jws, payload ): secret = "secret" jws_token = jws.encode(payload, secret, algorithm="HS256") jws.decode(jws_token, secret, algorithms=["HS256"]) with pytest.raises(InvalidAlgorithmError): jws.decode(jws_token, secret, algorithms=["HS384"]) def test_decode_works_with_unicode_token(self, jws): secret = "secret" unicode_jws = ( "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) jws.decode(unicode_jws, secret, algorithms=["HS256"]) def test_decode_missing_segments_throws_exception(self, jws): secret = "secret" example_jws = "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJoZWxsbyI6ICJ3b3JsZCJ9" # Missing segment with pytest.raises(DecodeError) as context: jws.decode(example_jws, secret, algorithms=["HS256"]) exception = context.value assert str(exception) == "Not enough segments" def test_decode_invalid_token_type_is_none(self, jws): example_jws = None example_secret = "secret" with pytest.raises(DecodeError) as context: jws.decode(example_jws, example_secret, algorithms=["HS256"]) exception = context.value assert "Invalid token type" in str(exception) def test_decode_invalid_token_type_is_int(self, jws): example_jws = 123 example_secret = "secret" with pytest.raises(DecodeError) as context: jws.decode(example_jws, example_secret, algorithms=["HS256"]) exception = context.value assert "Invalid token type" in str(exception) def test_decode_with_non_mapping_header_throws_exception(self, jws): secret = "secret" example_jws = ( "MQ" # == 1 ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) with pytest.raises(DecodeError) as context: jws.decode(example_jws, secret, algorithms=["HS256"]) exception = context.value assert str(exception) == "Invalid header string: must be a json object" def test_encode_algorithm_param_should_be_case_sensitive(self, jws, payload): jws.encode(payload, "secret", algorithm="HS256") with pytest.raises(NotImplementedError) as context: jws.encode(payload, None, algorithm="hs256") exception = context.value assert str(exception) == "Algorithm not supported" def test_encode_with_headers_alg_none(self, jws, payload): msg = jws.encode(payload, key=None, headers={"alg": "none"}) with pytest.raises(DecodeError) as context: jws.decode(msg, algorithms=["none"]) assert str(context.value) == "Signature verification failed" @crypto_required def test_encode_with_headers_alg_es256(self, jws, payload): with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file: priv_key = load_pem_private_key(ec_priv_file.read(), password=None) with open(key_path("testkey_ec.pub"), "rb") as ec_pub_file: pub_key = load_pem_public_key(ec_pub_file.read()) msg = jws.encode(payload, priv_key, headers={"alg": "ES256"}) assert b"hello world" == jws.decode(msg, pub_key, algorithms=["ES256"]) @crypto_required def test_encode_with_alg_hs256_and_headers_alg_es256(self, jws, payload): with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file: priv_key = load_pem_private_key(ec_priv_file.read(), password=None) with open(key_path("testkey_ec.pub"), "rb") as ec_pub_file: pub_key = load_pem_public_key(ec_pub_file.read()) msg = jws.encode(payload, priv_key, algorithm="HS256", headers={"alg": "ES256"}) assert b"hello world" == jws.decode(msg, pub_key, algorithms=["ES256"]) def test_decode_algorithm_param_should_be_case_sensitive(self, jws): example_jws = ( "eyJhbGciOiJoczI1NiIsInR5cCI6IkpXVCJ9" # alg = hs256 ".eyJoZWxsbyI6IndvcmxkIn0" ".5R_FEPE7SW2dT9GgIxPgZATjFGXfUDOSwo7TtO_Kd_g" ) with pytest.raises(InvalidAlgorithmError) as context: jws.decode(example_jws, "secret", algorithms=["hs256"]) exception = context.value assert str(exception) == "Algorithm not supported" def test_bad_secret(self, jws, payload): right_secret = "foo" bad_secret = "bar" jws_message = jws.encode(payload, right_secret) with pytest.raises(DecodeError) as excinfo: # Backward compat for ticket #315 jws.decode(jws_message, bad_secret, algorithms=["HS256"]) assert "Signature verification failed" == str(excinfo.value) with pytest.raises(InvalidSignatureError) as excinfo: jws.decode(jws_message, bad_secret, algorithms=["HS256"]) assert "Signature verification failed" == str(excinfo.value) def test_decodes_valid_jws(self, jws, payload): example_secret = "secret" example_jws = ( b"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." b"aGVsbG8gd29ybGQ." b"gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM" ) decoded_payload = jws.decode(example_jws, example_secret, algorithms=["HS256"]) assert decoded_payload == payload def test_decodes_complete_valid_jws(self, jws, payload): example_secret = "secret" example_jws = ( b"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." b"aGVsbG8gd29ybGQ." b"gEW0pdU4kxPthjtehYdhxB9mMOGajt1xCKlGGXDJ8PM" ) decoded = jws.decode_complete(example_jws, example_secret, algorithms=["HS256"]) assert decoded == { "header": {"alg": "HS256", "typ": "JWT"}, "payload": payload, "signature": ( b"\x80E\xb4\xa5\xd58\x93\x13\xed\x86;^\x85\x87a\xc4" b"\x1ff0\xe1\x9a\x8e\xddq\x08\xa9F\x19p\xc9\xf0\xf3" ), } # 'Control' Elliptic Curve jws created by another library. # Used to test for regressions that could affect both # encoding / decoding operations equally (causing tests # to still pass). @crypto_required def test_decodes_valid_es384_jws(self, jws): example_payload = {"hello": "world"} with open(key_path("testkey_ec.pub")) as fp: example_pubkey = fp.read() example_jws = ( b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." b"eyJoZWxsbyI6IndvcmxkIn0.TORyNQab_MoXM7DvNKaTwbrJr4UY" b"d2SsX8hhlnWelQFmPFSf_JzC2EbLnar92t-bXsDovzxp25ExazrVHkfPkQ" ) decoded_payload = jws.decode(example_jws, example_pubkey, algorithms=["ES256"]) json_payload = json.loads(decoded_payload) assert json_payload == example_payload # 'Control' RSA jws created by another library. # Used to test for regressions that could affect both # encoding / decoding operations equally (causing tests # to still pass). @crypto_required def test_decodes_valid_rs384_jws(self, jws): example_payload = {"hello": "world"} with open(key_path("testkey_rsa.pub")) as fp: example_pubkey = fp.read() example_jws = ( b"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9" b".eyJoZWxsbyI6IndvcmxkIn0" b".yNQ3nI9vEDs7lEh-Cp81McPuiQ4ZRv6FL4evTYYAh1X" b"lRTTR3Cz8pPA9Stgso8Ra9xGB4X3rlra1c8Jz10nTUju" b"O06OMm7oXdrnxp1KIiAJDerWHkQ7l3dlizIk1bmMA457" b"W2fNzNfHViuED5ISM081dgf_a71qBwJ_yShMMrSOfxDx" b"mX9c4DjRogRJG8SM5PvpLqI_Cm9iQPGMvmYK7gzcq2cJ" b"urHRJDJHTqIdpLWXkY7zVikeen6FhuGyn060Dz9gYq9t" b"uwmrtSWCBUjiN8sqJ00CDgycxKqHfUndZbEAOjcCAhBr" b"qWW3mSVivUfubsYbwUdUG3fSRPjaUPcpe8A" ) decoded_payload = jws.decode(example_jws, example_pubkey, algorithms=["RS384"]) json_payload = json.loads(decoded_payload) assert json_payload == example_payload def test_load_verify_valid_jws(self, jws, payload): example_secret = "secret" example_jws = ( b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." b"aGVsbG8gd29ybGQ." b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI" ) decoded_payload = jws.decode( example_jws, key=example_secret, algorithms=["HS256"] ) assert decoded_payload == payload def test_allow_skip_verification(self, jws, payload): right_secret = "foo" jws_message = jws.encode(payload, right_secret) decoded_payload = jws.decode(jws_message, options={"verify_signature": False}) assert decoded_payload == payload def test_decode_with_optional_algorithms(self, jws): example_secret = "secret" example_jws = ( b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." b"aGVsbG8gd29ybGQ." b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI" ) with pytest.raises(DecodeError) as exc: jws.decode(example_jws, key=example_secret) assert ( 'It is required that you pass in a value for the "algorithms" argument when calling decode().' in str(exc.value) ) def test_decode_no_algorithms_verify_signature_false(self, jws): example_secret = "secret" example_jws = ( b"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." b"aGVsbG8gd29ybGQ." b"SIr03zM64awWRdPrAM_61QWsZchAtgDV3pphfHPPWkI" ) jws.decode( example_jws, key=example_secret, options={"verify_signature": False}, ) def test_load_no_verification(self, jws, payload): right_secret = "foo" jws_message = jws.encode(payload, right_secret) decoded_payload = jws.decode( jws_message, key=None, algorithms=["HS256"], options={"verify_signature": False}, ) assert decoded_payload == payload def test_no_secret(self, jws, payload): right_secret = "foo" jws_message = jws.encode(payload, right_secret) with pytest.raises(DecodeError): jws.decode(jws_message, algorithms=["HS256"]) def test_verify_signature_with_no_secret(self, jws, payload): right_secret = "foo" jws_message = jws.encode(payload, right_secret) with pytest.raises(DecodeError) as exc: jws.decode(jws_message, algorithms=["HS256"]) assert "Signature verification" in str(exc.value) def test_verify_signature_with_no_algo_header_throws_exception(self, jws, payload): example_jws = b"e30.eyJhIjo1fQ.KEh186CjVw_Q8FadjJcaVnE7hO5Z9nHBbU8TgbhHcBY" with pytest.raises(InvalidAlgorithmError): jws.decode(example_jws, "secret", algorithms=["HS256"]) def test_invalid_crypto_alg(self, jws, payload): with pytest.raises(NotImplementedError): jws.encode(payload, "secret", algorithm="HS1024") @no_crypto_required def test_missing_crypto_library_better_error_messages(self, jws, payload): with pytest.raises(NotImplementedError) as excinfo: jws.encode(payload, "secret", algorithm="RS256") assert "cryptography" in str(excinfo.value) def test_unicode_secret(self, jws, payload): secret = "\xc2" jws_message = jws.encode(payload, secret) decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"]) assert decoded_payload == payload def test_nonascii_secret(self, jws, payload): secret = "\xc2" # char value that ascii codec cannot decode jws_message = jws.encode(payload, secret) decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"]) assert decoded_payload == payload def test_bytes_secret(self, jws, payload): secret = b"\xc2" # char value that ascii codec cannot decode jws_message = jws.encode(payload, secret) decoded_payload = jws.decode(jws_message, secret, algorithms=["HS256"]) assert decoded_payload == payload def test_decode_invalid_header_padding(self, jws): example_jws = ( "aeyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) example_secret = "secret" with pytest.raises(DecodeError) as exc: jws.decode(example_jws, example_secret, algorithms=["HS256"]) assert "header padding" in str(exc.value) def test_decode_invalid_header_string(self, jws): example_jws = ( "eyJhbGciOiAiSFMyNTbpIiwgInR5cCI6ICJKV1QifQ==" ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) example_secret = "secret" with pytest.raises(DecodeError) as exc: jws.decode(example_jws, example_secret, algorithms=["HS256"]) assert "Invalid header" in str(exc.value) def test_decode_invalid_payload_padding(self, jws): example_jws = ( "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" ".aeyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) example_secret = "secret" with pytest.raises(DecodeError) as exc: jws.decode(example_jws, example_secret, algorithms=["HS256"]) assert "Invalid payload padding" in str(exc.value) def test_decode_invalid_crypto_padding(self, jws): example_jws = ( "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".aatvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) example_secret = "secret" with pytest.raises(DecodeError) as exc: jws.decode(example_jws, example_secret, algorithms=["HS256"]) assert "Invalid crypto padding" in str(exc.value) def test_decode_with_algo_none_should_fail(self, jws, payload): jws_message = jws.encode(payload, key=None, algorithm=None) with pytest.raises(DecodeError): jws.decode(jws_message, algorithms=["none"]) def test_decode_with_algo_none_and_verify_false_should_pass(self, jws, payload): jws_message = jws.encode(payload, key=None, algorithm=None) jws.decode(jws_message, options={"verify_signature": False}) def test_get_unverified_header_returns_header_values(self, jws, payload): jws_message = jws.encode( payload, key="secret", algorithm="HS256", headers={"kid": "toomanysecrets"}, ) header = jws.get_unverified_header(jws_message) assert "kid" in header assert header["kid"] == "toomanysecrets" def test_get_unverified_header_fails_on_bad_header_types(self, jws, payload): # Contains a bad kid value (int 123 instead of string) example_jws = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6MTIzfQ" ".eyJzdWIiOiIxMjM0NTY3ODkwIn0" ".vs2WY54jfpKP3JGC73Vq5YlMsqM5oTZ1ZydT77SiZSk" ) with pytest.raises(InvalidTokenError) as exc: jws.get_unverified_header(example_jws) assert "Key ID header parameter must be a string" == str(exc.value) @pytest.mark.parametrize( "algo", [ "RS256", "RS384", "RS512", ], ) @crypto_required def test_encode_decode_rsa_related_algorithms(self, jws, payload, algo): # PEM-formatted RSA key with open(key_path("testkey_rsa.priv"), "rb") as rsa_priv_file: priv_rsakey = load_pem_private_key(rsa_priv_file.read(), password=None) jws_message = jws.encode(payload, priv_rsakey, algorithm=algo) with open(key_path("testkey_rsa.pub"), "rb") as rsa_pub_file: pub_rsakey = load_ssh_public_key(rsa_pub_file.read()) jws.decode(jws_message, pub_rsakey, algorithms=[algo]) # string-formatted key with open(key_path("testkey_rsa.priv")) as rsa_priv_file: priv_rsakey = rsa_priv_file.read() jws_message = jws.encode(payload, priv_rsakey, algorithm=algo) with open(key_path("testkey_rsa.pub")) as rsa_pub_file: pub_rsakey = rsa_pub_file.read() jws.decode(jws_message, pub_rsakey, algorithms=[algo]) def test_rsa_related_algorithms(self, jws): jws = PyJWS() jws_algorithms = jws.get_algorithms() if has_crypto: assert "RS256" in jws_algorithms assert "RS384" in jws_algorithms assert "RS512" in jws_algorithms assert "PS256" in jws_algorithms assert "PS384" in jws_algorithms assert "PS512" in jws_algorithms else: assert "RS256" not in jws_algorithms assert "RS384" not in jws_algorithms assert "RS512" not in jws_algorithms assert "PS256" not in jws_algorithms assert "PS384" not in jws_algorithms assert "PS512" not in jws_algorithms @pytest.mark.parametrize( "algo", [ "ES256", "ES256K", "ES384", "ES512", ], ) @crypto_required def test_encode_decode_ecdsa_related_algorithms(self, jws, payload, algo): # PEM-formatted EC key with open(key_path("testkey_ec.priv"), "rb") as ec_priv_file: priv_eckey = load_pem_private_key(ec_priv_file.read(), password=None) jws_message = jws.encode(payload, priv_eckey, algorithm=algo) with open(key_path("testkey_ec.pub"), "rb") as ec_pub_file: pub_eckey = load_pem_public_key(ec_pub_file.read()) jws.decode(jws_message, pub_eckey, algorithms=[algo]) # string-formatted key with open(key_path("testkey_ec.priv")) as ec_priv_file: priv_eckey = ec_priv_file.read() jws_message = jws.encode(payload, priv_eckey, algorithm=algo) with open(key_path("testkey_ec.pub")) as ec_pub_file: pub_eckey = ec_pub_file.read() jws.decode(jws_message, pub_eckey, algorithms=[algo]) def test_ecdsa_related_algorithms(self, jws): jws = PyJWS() jws_algorithms = jws.get_algorithms() if has_crypto: assert "ES256" in jws_algorithms assert "ES256K" in jws_algorithms assert "ES384" in jws_algorithms assert "ES512" in jws_algorithms else: assert "ES256" not in jws_algorithms assert "ES256K" not in jws_algorithms assert "ES384" not in jws_algorithms assert "ES512" not in jws_algorithms def test_skip_check_signature(self, jws): token = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJzb21lIjoicGF5bG9hZCJ9" ".4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZA" ) jws.decode(token, "secret", options={"verify_signature": False}) def test_decode_options_must_be_dict(self, jws, payload): token = jws.encode(payload, "secret") with pytest.raises(TypeError): jws.decode(token, "secret", options=object()) with pytest.raises(TypeError): jws.decode(token, "secret", options="something") def test_custom_json_encoder(self, jws, payload): class CustomJSONEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, Decimal): return "it worked" return super().default(o) data = {"some_decimal": Decimal("2.2")} with pytest.raises(TypeError): jws.encode(payload, "secret", headers=data) token = jws.encode( payload, "secret", headers=data, json_encoder=CustomJSONEncoder ) header, *_ = token.split(".") header = json.loads(base64url_decode(header)) assert "some_decimal" in header assert header["some_decimal"] == "it worked" def test_encode_headers_parameter_adds_headers(self, jws, payload): headers = {"testheader": True} token = jws.encode(payload, "secret", headers=headers) if not isinstance(token, str): token = token.decode() header = token[0 : token.index(".")].encode() header = base64url_decode(header) if not isinstance(header, str): header = header.decode() header_obj = json.loads(header) assert "testheader" in header_obj assert header_obj["testheader"] == headers["testheader"] def test_encode_with_typ(self, jws): payload = """ { "iss": "https://scim.example.com", "iat": 1458496404, "jti": "4d3559ec67504aaba65d40b0363faad8", "aud": [ "https://scim.example.com/Feeds/98d52461fa5bbc879593b7754", "https://scim.example.com/Feeds/5d7604516b1d08641d7676ee7" ], "events": { "urn:ietf:params:scim:event:create": { "ref": "https://scim.example.com/Users/44f6142df96bd6ab61e7521d9", "attributes": ["id", "name", "userName", "password", "emails"] } } } """ token = jws.encode( payload.encode("utf-8"), "secret", headers={"typ": "secevent+jwt"} ) header = token[0 : token.index(".")].encode() header = base64url_decode(header) header_obj = json.loads(header) assert "typ" in header_obj assert header_obj["typ"] == "secevent+jwt" def test_encode_with_typ_empty_string(self, jws, payload): token = jws.encode(payload, "secret", headers={"typ": ""}) header = token[0 : token.index(".")].encode() header = base64url_decode(header) header_obj = json.loads(header) assert "typ" not in header_obj def test_encode_with_typ_none(self, jws, payload): token = jws.encode(payload, "secret", headers={"typ": None}) header = token[0 : token.index(".")].encode() header = base64url_decode(header) header_obj = json.loads(header) assert "typ" not in header_obj def test_encode_with_typ_without_keywords(self, jws, payload): headers = {"foo": "bar"} token = jws.encode(payload, "secret", "HS256", headers, None) header = token[0 : token.index(".")].encode() header = base64url_decode(header) header_obj = json.loads(header) assert "foo" in header_obj assert header_obj["foo"] == "bar" def test_encode_fails_on_invalid_kid_types(self, jws, payload): with pytest.raises(InvalidTokenError) as exc: jws.encode(payload, "secret", headers={"kid": 123}) assert "Key ID header parameter must be a string" == str(exc.value) with pytest.raises(InvalidTokenError) as exc: jws.encode(payload, "secret", headers={"kid": None}) assert "Key ID header parameter must be a string" == str(exc.value) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1634387019.0 PyJWT-2.3.0/tests/test_api_jwt.py0000644000076500000240000005415300000000000017615 0ustar00jpadillastaff00000000000000import json import time from calendar import timegm from datetime import datetime, timedelta, timezone from decimal import Decimal import pytest from jwt.api_jwt import PyJWT from jwt.exceptions import ( DecodeError, ExpiredSignatureError, ImmatureSignatureError, InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError, MissingRequiredClaimError, ) from jwt.utils import base64url_decode from .utils import crypto_required, key_path, utc_timestamp @pytest.fixture def jwt(): return PyJWT() @pytest.fixture def payload(): """Creates a sample JWT claimset for use as a payload during tests""" return {"iss": "jeff", "exp": utc_timestamp() + 15, "claim": "insanity"} class TestJWT: def test_decodes_valid_jwt(self, jwt): example_payload = {"hello": "world"} example_secret = "secret" example_jwt = ( b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" b".eyJoZWxsbyI6ICJ3b3JsZCJ9" b".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) decoded_payload = jwt.decode(example_jwt, example_secret, algorithms=["HS256"]) assert decoded_payload == example_payload def test_decodes_complete_valid_jwt(self, jwt): example_payload = {"hello": "world"} example_secret = "secret" example_jwt = ( b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" b".eyJoZWxsbyI6ICJ3b3JsZCJ9" b".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) decoded = jwt.decode_complete(example_jwt, example_secret, algorithms=["HS256"]) assert decoded == { "header": {"alg": "HS256", "typ": "JWT"}, "payload": example_payload, "signature": ( b'\xb6\xf6\xa0,2\xe8j"J\xc4\xe2\xaa\xa4\x15\xd2' b"\x10l\xbbI\x84\xa2}\x98c\x9e\xd8&\xf5\xcbi\xca?" ), } def test_load_verify_valid_jwt(self, jwt): example_payload = {"hello": "world"} example_secret = "secret" example_jwt = ( b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" b".eyJoZWxsbyI6ICJ3b3JsZCJ9" b".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) decoded_payload = jwt.decode( example_jwt, key=example_secret, algorithms=["HS256"] ) assert decoded_payload == example_payload def test_decode_invalid_payload_string(self, jwt): example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.aGVsb" "G8gd29ybGQ.SIr03zM64awWRdPrAM_61QWsZchAtgDV" "3pphfHPPWkI" ) example_secret = "secret" with pytest.raises(DecodeError) as exc: jwt.decode(example_jwt, example_secret, algorithms=["HS256"]) assert "Invalid payload string" in str(exc.value) def test_decode_with_non_mapping_payload_throws_exception(self, jwt): secret = "secret" example_jwt = ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." "MQ." # == 1 "AbcSR3DWum91KOgfKxUHm78rLs_DrrZ1CrDgpUFFzls" ) with pytest.raises(DecodeError) as context: jwt.decode(example_jwt, secret, algorithms=["HS256"]) exception = context.value assert str(exception) == "Invalid payload string: must be a json object" def test_decode_with_invalid_audience_param_throws_exception(self, jwt): secret = "secret" example_jwt = ( "eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" ".eyJoZWxsbyI6ICJ3b3JsZCJ9" ".tvagLDLoaiJKxOKqpBXSEGy7SYSifZhjntgm9ctpyj8" ) with pytest.raises(TypeError) as context: jwt.decode(example_jwt, secret, audience=1, algorithms=["HS256"]) exception = context.value assert str(exception) == "audience must be a string, iterable, or None" def test_decode_with_nonlist_aud_claim_throws_exception(self, jwt): secret = "secret" example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJoZWxsbyI6IndvcmxkIiwiYXVkIjoxfQ" # aud = 1 ".Rof08LBSwbm8Z_bhA2N3DFY-utZR1Gi9rbIS5Zthnnc" ) with pytest.raises(InvalidAudienceError) as context: jwt.decode( example_jwt, secret, audience="my_audience", algorithms=["HS256"], ) exception = context.value assert str(exception) == "Invalid claim format in token" def test_decode_with_invalid_aud_list_member_throws_exception(self, jwt): secret = "secret" example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJoZWxsbyI6IndvcmxkIiwiYXVkIjpbMV19" ".iQgKpJ8shetwNMIosNXWBPFB057c2BHs-8t1d2CCM2A" ) with pytest.raises(InvalidAudienceError) as context: jwt.decode( example_jwt, secret, audience="my_audience", algorithms=["HS256"], ) exception = context.value assert str(exception) == "Invalid claim format in token" def test_encode_bad_type(self, jwt): types = ["string", tuple(), list(), 42, set()] for t in types: pytest.raises( TypeError, lambda: jwt.encode(t, "secret", algorithms=["HS256"]), ) def test_encode_with_typ(self, jwt): payload = { "iss": "https://scim.example.com", "iat": 1458496404, "jti": "4d3559ec67504aaba65d40b0363faad8", "aud": [ "https://scim.example.com/Feeds/98d52461fa5bbc879593b7754", "https://scim.example.com/Feeds/5d7604516b1d08641d7676ee7", ], "events": { "urn:ietf:params:scim:event:create": { "ref": "https://scim.example.com/Users/44f6142df96bd6ab61e7521d9", "attributes": ["id", "name", "userName", "password", "emails"], } }, } token = jwt.encode( payload, "secret", algorithm="HS256", headers={"typ": "secevent+jwt"} ) header = token[0 : token.index(".")].encode() header = base64url_decode(header) header_obj = json.loads(header) assert "typ" in header_obj assert header_obj["typ"] == "secevent+jwt" def test_decode_raises_exception_if_exp_is_not_int(self, jwt): # >>> jwt.encode({'exp': 'not-an-int'}, 'secret') example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJleHAiOiJub3QtYW4taW50In0." "P65iYgoHtBqB07PMtBSuKNUEIPPPfmjfJG217cEE66s" ) with pytest.raises(DecodeError) as exc: jwt.decode(example_jwt, "secret", algorithms=["HS256"]) assert "exp" in str(exc.value) def test_decode_raises_exception_if_iat_is_not_int(self, jwt): # >>> jwt.encode({'iat': 'not-an-int'}, 'secret') example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJpYXQiOiJub3QtYW4taW50In0." "H1GmcQgSySa5LOKYbzGm--b1OmRbHFkyk8pq811FzZM" ) with pytest.raises(InvalidIssuedAtError): jwt.decode(example_jwt, "secret", algorithms=["HS256"]) def test_decode_raises_exception_if_nbf_is_not_int(self, jwt): # >>> jwt.encode({'nbf': 'not-an-int'}, 'secret') example_jwt = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." "eyJuYmYiOiJub3QtYW4taW50In0." "c25hldC8G2ZamC8uKpax9sYMTgdZo3cxrmzFHaAAluw" ) with pytest.raises(DecodeError): jwt.decode(example_jwt, "secret", algorithms=["HS256"]) def test_decode_raises_exception_if_aud_is_none(self, jwt): # >>> jwt.encode({'aud': None}, 'secret') example_jwt = ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." "eyJhdWQiOm51bGx9." "-Peqc-pTugGvrc5C8Bnl0-X1V_5fv-aVb_7y7nGBVvQ" ) decoded = jwt.decode(example_jwt, "secret", algorithms=["HS256"]) assert decoded["aud"] is None def test_encode_datetime(self, jwt): secret = "secret" current_datetime = datetime.now(tz=timezone.utc) payload = { "exp": current_datetime, "iat": current_datetime, "nbf": current_datetime, } jwt_message = jwt.encode(payload, secret) decoded_payload = jwt.decode( jwt_message, secret, leeway=1, algorithms=["HS256"] ) assert decoded_payload["exp"] == timegm(current_datetime.utctimetuple()) assert decoded_payload["iat"] == timegm(current_datetime.utctimetuple()) assert decoded_payload["nbf"] == timegm(current_datetime.utctimetuple()) # payload is not mutated. assert payload == { "exp": current_datetime, "iat": current_datetime, "nbf": current_datetime, } # 'Control' Elliptic Curve JWT created by another library. # Used to test for regressions that could affect both # encoding / decoding operations equally (causing tests # to still pass). @crypto_required def test_decodes_valid_es256_jwt(self, jwt): example_payload = {"hello": "world"} with open(key_path("testkey_ec.pub")) as fp: example_pubkey = fp.read() example_jwt = ( b"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9." b"eyJoZWxsbyI6IndvcmxkIn0.TORyNQab_MoXM7DvNKaTwbrJr4UY" b"d2SsX8hhlnWelQFmPFSf_JzC2EbLnar92t-bXsDovzxp25ExazrVHkfPkQ" ) decoded_payload = jwt.decode(example_jwt, example_pubkey, algorithms=["ES256"]) assert decoded_payload == example_payload # 'Control' RSA JWT created by another library. # Used to test for regressions that could affect both # encoding / decoding operations equally (causing tests # to still pass). @crypto_required def test_decodes_valid_rs384_jwt(self, jwt): example_payload = {"hello": "world"} with open(key_path("testkey_rsa.pub")) as fp: example_pubkey = fp.read() example_jwt = ( b"eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9" b".eyJoZWxsbyI6IndvcmxkIn0" b".yNQ3nI9vEDs7lEh-Cp81McPuiQ4ZRv6FL4evTYYAh1X" b"lRTTR3Cz8pPA9Stgso8Ra9xGB4X3rlra1c8Jz10nTUju" b"O06OMm7oXdrnxp1KIiAJDerWHkQ7l3dlizIk1bmMA457" b"W2fNzNfHViuED5ISM081dgf_a71qBwJ_yShMMrSOfxDx" b"mX9c4DjRogRJG8SM5PvpLqI_Cm9iQPGMvmYK7gzcq2cJ" b"urHRJDJHTqIdpLWXkY7zVikeen6FhuGyn060Dz9gYq9t" b"uwmrtSWCBUjiN8sqJ00CDgycxKqHfUndZbEAOjcCAhBr" b"qWW3mSVivUfubsYbwUdUG3fSRPjaUPcpe8A" ) decoded_payload = jwt.decode(example_jwt, example_pubkey, algorithms=["RS384"]) assert decoded_payload == example_payload def test_decode_with_expiration(self, jwt, payload): payload["exp"] = utc_timestamp() - 1 secret = "secret" jwt_message = jwt.encode(payload, secret) with pytest.raises(ExpiredSignatureError): jwt.decode(jwt_message, secret, algorithms=["HS256"]) def test_decode_with_notbefore(self, jwt, payload): payload["nbf"] = utc_timestamp() + 10 secret = "secret" jwt_message = jwt.encode(payload, secret) with pytest.raises(ImmatureSignatureError): jwt.decode(jwt_message, secret, algorithms=["HS256"]) def test_decode_skip_expiration_verification(self, jwt, payload): payload["exp"] = time.time() - 1 secret = "secret" jwt_message = jwt.encode(payload, secret) jwt.decode( jwt_message, secret, algorithms=["HS256"], options={"verify_exp": False}, ) def test_decode_skip_notbefore_verification(self, jwt, payload): payload["nbf"] = time.time() + 10 secret = "secret" jwt_message = jwt.encode(payload, secret) jwt.decode( jwt_message, secret, algorithms=["HS256"], options={"verify_nbf": False}, ) def test_decode_with_expiration_with_leeway(self, jwt, payload): payload["exp"] = utc_timestamp() - 2 secret = "secret" jwt_message = jwt.encode(payload, secret) # With 3 seconds leeway, should be ok for leeway in (3, timedelta(seconds=3)): decoded = jwt.decode( jwt_message, secret, leeway=leeway, algorithms=["HS256"] ) assert decoded == payload # With 1 seconds, should fail for leeway in (1, timedelta(seconds=1)): with pytest.raises(ExpiredSignatureError): jwt.decode(jwt_message, secret, leeway=leeway, algorithms=["HS256"]) def test_decode_with_notbefore_with_leeway(self, jwt, payload): payload["nbf"] = utc_timestamp() + 10 secret = "secret" jwt_message = jwt.encode(payload, secret) # With 13 seconds leeway, should be ok jwt.decode(jwt_message, secret, leeway=13, algorithms=["HS256"]) with pytest.raises(ImmatureSignatureError): jwt.decode(jwt_message, secret, leeway=1, algorithms=["HS256"]) def test_check_audience_when_valid(self, jwt): payload = {"some": "payload", "aud": "urn:me"} token = jwt.encode(payload, "secret") jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"]) def test_check_audience_list_when_valid(self, jwt): payload = {"some": "payload", "aud": "urn:me"} token = jwt.encode(payload, "secret") jwt.decode( token, "secret", audience=["urn:you", "urn:me"], algorithms=["HS256"], ) def test_check_audience_none_specified(self, jwt): payload = {"some": "payload", "aud": "urn:me"} token = jwt.encode(payload, "secret") with pytest.raises(InvalidAudienceError): jwt.decode(token, "secret", algorithms=["HS256"]) def test_raise_exception_invalid_audience_list(self, jwt): payload = {"some": "payload", "aud": "urn:me"} token = jwt.encode(payload, "secret") with pytest.raises(InvalidAudienceError): jwt.decode( token, "secret", audience=["urn:you", "urn:him"], algorithms=["HS256"], ) def test_check_audience_in_array_when_valid(self, jwt): payload = {"some": "payload", "aud": ["urn:me", "urn:someone-else"]} token = jwt.encode(payload, "secret") jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"]) def test_raise_exception_invalid_audience(self, jwt): payload = {"some": "payload", "aud": "urn:someone-else"} token = jwt.encode(payload, "secret") with pytest.raises(InvalidAudienceError): jwt.decode(token, "secret", audience="urn-me", algorithms=["HS256"]) def test_raise_exception_invalid_audience_in_array(self, jwt): payload = { "some": "payload", "aud": ["urn:someone", "urn:someone-else"], } token = jwt.encode(payload, "secret") with pytest.raises(InvalidAudienceError): jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"]) def test_raise_exception_token_without_issuer(self, jwt): issuer = "urn:wrong" payload = {"some": "payload"} token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"]) assert exc.value.claim == "iss" def test_raise_exception_token_without_audience(self, jwt): payload = {"some": "payload"} token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"]) assert exc.value.claim == "aud" def test_raise_exception_token_with_aud_none_and_without_audience(self, jwt): payload = {"some": "payload", "aud": None} token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode(token, "secret", audience="urn:me", algorithms=["HS256"]) assert exc.value.claim == "aud" def test_check_issuer_when_valid(self, jwt): issuer = "urn:foo" payload = {"some": "payload", "iss": "urn:foo"} token = jwt.encode(payload, "secret") jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"]) def test_raise_exception_invalid_issuer(self, jwt): issuer = "urn:wrong" payload = {"some": "payload", "iss": "urn:foo"} token = jwt.encode(payload, "secret") with pytest.raises(InvalidIssuerError): jwt.decode(token, "secret", issuer=issuer, algorithms=["HS256"]) def test_skip_check_audience(self, jwt): payload = {"some": "payload", "aud": "urn:me"} token = jwt.encode(payload, "secret") jwt.decode( token, "secret", options={"verify_aud": False}, algorithms=["HS256"], ) def test_skip_check_exp(self, jwt): payload = { "some": "payload", "exp": datetime.now(tz=timezone.utc) - timedelta(days=1), } token = jwt.encode(payload, "secret") jwt.decode( token, "secret", options={"verify_exp": False}, algorithms=["HS256"], ) def test_decode_should_raise_error_if_exp_required_but_not_present(self, jwt): payload = { "some": "payload", # exp not present } token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode( token, "secret", options={"require": ["exp"]}, algorithms=["HS256"], ) assert exc.value.claim == "exp" def test_decode_should_raise_error_if_iat_required_but_not_present(self, jwt): payload = { "some": "payload", # iat not present } token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode( token, "secret", options={"require": ["iat"]}, algorithms=["HS256"], ) assert exc.value.claim == "iat" def test_decode_should_raise_error_if_nbf_required_but_not_present(self, jwt): payload = { "some": "payload", # nbf not present } token = jwt.encode(payload, "secret") with pytest.raises(MissingRequiredClaimError) as exc: jwt.decode( token, "secret", options={"require": ["nbf"]}, algorithms=["HS256"], ) assert exc.value.claim == "nbf" def test_skip_check_signature(self, jwt): token = ( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ".eyJzb21lIjoicGF5bG9hZCJ9" ".4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZA" ) jwt.decode( token, "secret", options={"verify_signature": False}, algorithms=["HS256"], ) def test_skip_check_iat(self, jwt): payload = { "some": "payload", "iat": datetime.now(tz=timezone.utc) + timedelta(days=1), } token = jwt.encode(payload, "secret") jwt.decode( token, "secret", options={"verify_iat": False}, algorithms=["HS256"], ) def test_skip_check_nbf(self, jwt): payload = { "some": "payload", "nbf": datetime.now(tz=timezone.utc) + timedelta(days=1), } token = jwt.encode(payload, "secret") jwt.decode( token, "secret", options={"verify_nbf": False}, algorithms=["HS256"], ) def test_custom_json_encoder(self, jwt): class CustomJSONEncoder(json.JSONEncoder): def default(self, o): if isinstance(o, Decimal): return "it worked" return super().default(o) data = {"some_decimal": Decimal("2.2")} with pytest.raises(TypeError): jwt.encode(data, "secret", algorithms=["HS256"]) token = jwt.encode(data, "secret", json_encoder=CustomJSONEncoder) payload = jwt.decode(token, "secret", algorithms=["HS256"]) assert payload == {"some_decimal": "it worked"} def test_decode_with_verify_exp_option(self, jwt, payload): payload["exp"] = utc_timestamp() - 1 secret = "secret" jwt_message = jwt.encode(payload, secret) jwt.decode( jwt_message, secret, algorithms=["HS256"], options={"verify_exp": False}, ) with pytest.raises(ExpiredSignatureError): jwt.decode( jwt_message, secret, algorithms=["HS256"], options={"verify_exp": True}, ) def test_decode_with_verify_exp_option_and_signature_off(self, jwt, payload): payload["exp"] = utc_timestamp() - 1 secret = "secret" jwt_message = jwt.encode(payload, secret) jwt.decode( jwt_message, options={"verify_signature": False}, ) with pytest.raises(ExpiredSignatureError): jwt.decode( jwt_message, options={"verify_signature": False, "verify_exp": True}, ) def test_decode_with_optional_algorithms(self, jwt, payload): secret = "secret" jwt_message = jwt.encode(payload, secret) with pytest.raises(DecodeError) as exc: jwt.decode(jwt_message, secret) assert ( 'It is required that you pass in a value for the "algorithms" argument when calling decode().' in str(exc.value) ) def test_decode_no_algorithms_verify_signature_false(self, jwt, payload): secret = "secret" jwt_message = jwt.encode(payload, secret) jwt.decode(jwt_message, secret, options={"verify_signature": False}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1571759864.0 PyJWT-2.3.0/tests/test_exceptions.py0000644000076500000240000000032500000000000020331 0ustar00jpadillastaff00000000000000from jwt.exceptions import MissingRequiredClaimError def test_missing_required_claim_error_has_proper_str(): exc = MissingRequiredClaimError("abc") assert str(exc) == 'Token is missing the "abc" claim' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/test_jwks_client.py0000644000076500000240000001634600000000000020476 0ustar00jpadillastaff00000000000000import contextlib import json from unittest import mock import pytest import jwt from jwt import PyJWKClient from jwt.api_jwk import PyJWK from jwt.exceptions import PyJWKClientError from .utils import crypto_required RESPONSE_DATA = { "keys": [ { "alg": "RS256", "kty": "RSA", "use": "sig", "n": "0wtlJRY9-ru61LmOgieeI7_rD1oIna9QpBMAOWw8wTuoIhFQFwcIi7MFB7IEfelCPj08vkfLsuFtR8cG07EE4uvJ78bAqRjMsCvprWp4e2p7hqPnWcpRpDEyHjzirEJle1LPpjLLVaSWgkbrVaOD0lkWkP1T1TkrOset_Obh8BwtO-Ww-UfrEwxTyz1646AGkbT2nL8PX0trXrmira8GnrCkFUgTUS61GoTdb9bCJ19PLX9Gnxw7J0BtR0GubopXq8KlI0ThVql6ZtVGN2dvmrCPAVAZleM5TVB61m0VSXvGWaF6_GeOhbFoyWcyUmFvzWhBm8Q38vWgsSI7oHTkEw", "e": "AQAB", "kid": "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw", "x5t": "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw", "x5c": [ "MIIDBzCCAe+gAwIBAgIJNtD9Ozi6j2jJMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMTFmRldi04N2V2eDlydS5hdXRoMC5jb20wHhcNMTkwNjIwMTU0NDU4WhcNMzMwMjI2MTU0NDU4WjAhMR8wHQYDVQQDExZkZXYtODdldng5cnUuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0wtlJRY9+ru61LmOgieeI7/rD1oIna9QpBMAOWw8wTuoIhFQFwcIi7MFB7IEfelCPj08vkfLsuFtR8cG07EE4uvJ78bAqRjMsCvprWp4e2p7hqPnWcpRpDEyHjzirEJle1LPpjLLVaSWgkbrVaOD0lkWkP1T1TkrOset/Obh8BwtO+Ww+UfrEwxTyz1646AGkbT2nL8PX0trXrmira8GnrCkFUgTUS61GoTdb9bCJ19PLX9Gnxw7J0BtR0GubopXq8KlI0ThVql6ZtVGN2dvmrCPAVAZleM5TVB61m0VSXvGWaF6/GeOhbFoyWcyUmFvzWhBm8Q38vWgsSI7oHTkEwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQlGXpmYaXFB7Q3eG69Uhjd4cFp/jAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAIzQOF/h4T5WWAdjhcIwdNS7hS2Deq+UxxkRv+uavj6O9mHLuRG1q5onvSFShjECXaYT6OGibn7Ufw/JSm3+86ZouMYjBEqGh4OvWRkwARy1YTWUVDGpT2HAwtIq3lfYvhe8P4VfZByp1N4lfn6X2NcJflG+Q+mfXNmRFyyft3Oq51PCZyyAkU7bTun9FmMOyBtmJvQjZ8RXgBLvu9nUcZB8yTVoeUEg4cLczQlli/OkiFXhWgrhVr8uF0/9klslMFXtm78iYSgR8/oC+k1pSNd1+ESSt7n6+JiAQ2Co+ZNKta7LTDGAjGjNDymyoCrZpeuYQwwnHYEHu/0khjAxhXo=" ], } ] } @contextlib.contextmanager def mocked_response(data): with mock.patch("urllib.request.urlopen") as urlopen_mock: response = mock.Mock() response.__enter__ = mock.Mock(return_value=response) response.__exit__ = mock.Mock() response.read.side_effect = [json.dumps(data)] urlopen_mock.return_value = response yield urlopen_mock @crypto_required class TestPyJWKClient: def test_get_jwk_set(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" with mocked_response(RESPONSE_DATA): jwks_client = PyJWKClient(url) jwk_set = jwks_client.get_jwk_set() assert len(jwk_set.keys) == 1 def test_get_signing_keys(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" with mocked_response(RESPONSE_DATA): jwks_client = PyJWKClient(url) signing_keys = jwks_client.get_signing_keys() assert len(signing_keys) == 1 assert isinstance(signing_keys[0], PyJWK) def test_get_signing_keys_if_no_use_provided(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" mocked_key = RESPONSE_DATA["keys"][0].copy() del mocked_key["use"] response = {"keys": [mocked_key]} with mocked_response(response): jwks_client = PyJWKClient(url) signing_keys = jwks_client.get_signing_keys() assert len(signing_keys) == 1 assert isinstance(signing_keys[0], PyJWK) def test_get_signing_keys_raises_if_none_found(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" mocked_key = RESPONSE_DATA["keys"][0].copy() mocked_key["use"] = "enc" response = {"keys": [mocked_key]} with mocked_response(response): jwks_client = PyJWKClient(url) with pytest.raises(PyJWKClientError) as exc: jwks_client.get_signing_keys() assert "The JWKS endpoint did not contain any signing keys" in str(exc.value) def test_get_signing_key(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" with mocked_response(RESPONSE_DATA): jwks_client = PyJWKClient(url) signing_key = jwks_client.get_signing_key(kid) assert isinstance(signing_key, PyJWK) assert signing_key.key_type == "RSA" assert signing_key.key_id == kid assert signing_key.public_key_use == "sig" def test_get_signing_key_caches_result(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" jwks_client = PyJWKClient(url) with mocked_response(RESPONSE_DATA): jwks_client.get_signing_key(kid) # mocked_response does not allow urllib.request.urlopen to be called twice # so a second mock is needed with mocked_response(RESPONSE_DATA) as repeated_call: jwks_client.get_signing_key(kid) assert repeated_call.call_count == 0 def test_get_signing_key_does_not_cache_opt_out(self): url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" jwks_client = PyJWKClient(url, cache_keys=False) with mocked_response(RESPONSE_DATA): jwks_client.get_signing_key(kid) # mocked_response does not allow urllib.request.urlopen to be called twice # so a second mock is needed with mocked_response(RESPONSE_DATA) as repeated_call: jwks_client.get_signing_key(kid) assert repeated_call.call_count == 1 def test_get_signing_key_from_jwt(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA" url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" with mocked_response(RESPONSE_DATA): jwks_client = PyJWKClient(url) signing_key = jwks_client.get_signing_key_from_jwt(token) data = jwt.decode( token, signing_key.key, algorithms=["RS256"], audience="https://expenses-api", options={"verify_exp": False}, ) assert data == { "iss": "https://dev-87evx9ru.auth0.com/", "sub": "aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC@clients", "aud": "https://expenses-api", "iat": 1572006954, "exp": 1572006964, "azp": "aW4Cca79xReLWUz0aE2H6kD0O3cXBVtC", "gty": "client-credentials", } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1598286175.0 PyJWT-2.3.0/tests/test_jwt.py0000644000076500000240000000116100000000000016753 0ustar00jpadillastaff00000000000000import jwt from .utils import utc_timestamp def test_encode_decode(): """ This test exists primarily to ensure that calls to jwt.encode and jwt.decode don't explode. Most functionality is tested by the PyJWT class tests. This is primarily a sanity check to make sure we don't break the public global functions. """ payload = {"iss": "jeff", "exp": utc_timestamp() + 15, "claim": "insanity"} secret = "secret" jwt_message = jwt.encode(payload, secret, algorithm="HS256") decoded_payload = jwt.decode(jwt_message, secret, algorithms=["HS256"]) assert decoded_payload == payload ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608422497.0 PyJWT-2.3.0/tests/test_utils.py0000644000076500000240000000160100000000000017306 0ustar00jpadillastaff00000000000000import pytest from jwt.utils import force_bytes, from_base64url_uint, to_base64url_uint @pytest.mark.parametrize( "inputval,expected", [ (0, b"AA"), (1, b"AQ"), (255, b"_w"), (65537, b"AQAB"), (123456789, b"B1vNFQ"), pytest.param(-1, "", marks=pytest.mark.xfail(raises=ValueError)), ], ) def test_to_base64url_uint(inputval, expected): actual = to_base64url_uint(inputval) assert actual == expected @pytest.mark.parametrize( "inputval,expected", [ (b"AA", 0), (b"AQ", 1), (b"_w", 255), (b"AQAB", 65537), (b"B1vNFQ", 123456789), ], ) def test_from_base64url_uint(inputval, expected): actual = from_base64url_uint(inputval) assert actual == expected def test_force_bytes_raises_error_on_invalid_object(): with pytest.raises(TypeError): force_bytes({}) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1633516636.0 PyJWT-2.3.0/tests/utils.py0000644000076500000240000000132200000000000016247 0ustar00jpadillastaff00000000000000import os from calendar import timegm from datetime import datetime, timezone import pytest from jwt.algorithms import has_crypto def utc_timestamp(): return timegm(datetime.now(tz=timezone.utc).utctimetuple()) def key_path(key_name): return os.path.join(os.path.dirname(os.path.realpath(__file__)), "keys", key_name) def no_crypto_required(class_or_func): decorator = pytest.mark.skipif( has_crypto, reason="Requires cryptography library not installed", ) return decorator(class_or_func) def crypto_required(class_or_func): decorator = pytest.mark.skipif( not has_crypto, reason="Requires cryptography library installed" ) return decorator(class_or_func) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1610456812.0 PyJWT-2.3.0/tox.ini0000644000076500000240000000263300000000000014714 0ustar00jpadillastaff00000000000000[pytest] addopts = -ra testpaths = tests filterwarnings = once::Warning ignore:::pympler[.*] [gh-actions] python = 3.6: py36 3.7: py37, docs 3.8: py38, typing 3.9: py39 [tox] envlist = lint typing py{36,37,38,39}-{crypto,nocrypto} docs pypi-description coverage-report isolated_build = True [testenv] # Prevent random setuptools/pip breakages like # https://github.com/pypa/setuptools/issues/1042 from breaking our builds. setenv = VIRTUALENV_NO_DOWNLOAD=1 extras = tests crypto: crypto commands = {envpython} -b -m coverage run -m pytest {posargs} [testenv:docs] basepython = python3.7 extras = docs commands = sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html python -m doctest README.rst [testenv:typing] basepython = python3.8 extras = dev commands = mypy jwt [testenv:lint] basepython = python3.8 extras = dev passenv = HOMEPATH # needed on Windows commands = pre-commit run --all-files [testenv:pypi-description] basepython = python3.8 skip_install = true deps = twine pip >= 18.0.0 commands = pip wheel -w {envtmpdir}/build --no-deps . twine check {envtmpdir}/build/* [testenv:coverage-report] basepython = python3.8 skip_install = true deps = coverage[toml]==5.0.4 commands = coverage combine coverage report