From 374f6cfe3162c44f5d862e9909c7e7fc7bc417f8 Mon Sep 17 00:00:00 2001 From: roland Date: Fri, 4 Aug 2023 10:06:44 +0200 Subject: [PATCH 01/12] Chnages necessary for the SD JWT implementation. SD==Selective Disclosure (https://datatracker.ietf.org/doc/draft-ietf-oauth-selective-disclosure-jwt/) --- src/cryptojwt/jwt.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index e212c77..edb1dac 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -4,6 +4,8 @@ import time import uuid from json import JSONDecodeError +from typing import Dict +from typing import Optional from .exception import HeaderError from .exception import VerificationError @@ -97,7 +99,7 @@ def __init__( ): self.key_jar = key_jar # KeyJar instance self.iss = iss # My identifier - self.lifetime = lifetime # default life time of the signature + self.lifetime = lifetime # default lifetime of the signature self.sign = sign # default signing or not self.alg = sign_alg # default signing algorithm self.encrypt = encrypt # default encrypting or not @@ -206,16 +208,30 @@ def pack_key(self, issuer_id="", kid=""): return keys[0] # Might be more then one if kid == '' - def pack(self, payload=None, kid="", issuer_id="", recv="", aud=None, iat=None, **kwargs): + def message(self, signing_key, **kwargs): + return json.dumps(kwargs) + + def pack( + self, + payload: Optional[dict] = None, + kid: Optional[str] = "", + issuer_id: Optional[str] = "", + recv: Optional[str] = "", + aud: Optional[str] = None, + iat: Optional[int] = None, + jws_headers: Dict[str, str] = None, + **kwargs + ) -> str: """ :param payload: Information to be carried as payload in the JWT :param kid: Key ID - :param issuer_id: The owner of the the keys that are to be used for signing + :param issuer_id: The owner of the keys that are to be used for signing :param recv: The intended immediate receiver :param aud: Intended audience for this JWS/JWE, not expected to contain the recipient. :param iat: Override issued at (default current timestamp) + :param jws_headers: JWS headers :param kwargs: Extra keyword arguments :return: A signed or signed and encrypted Json Web Token """ @@ -249,10 +265,10 @@ def pack(self, payload=None, kid="", issuer_id="", recv="", aud=None, iat=None, else: _key = None - _jws = JWS(json.dumps(_args), alg=self.alg) + _jws = JWS(self.message(signing_key=_key, **_args), alg=self.alg, **jws_headers) _sjwt = _jws.sign_compact([_key]) else: - _sjwt = json.dumps(_args) + _sjwt = self.message(signing_key=None, **_args) if _encrypt: if not self.sign: From 98bcf0583d0fc4b2d8c3455a307a6d727bba49f3 Mon Sep 17 00:00:00 2001 From: roland Date: Fri, 4 Aug 2023 10:08:18 +0200 Subject: [PATCH 02/12] Bumped version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9536b46..247d395 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ exclude_lines = [ [tool.poetry] name = "cryptojwt" -version = "1.8.3" +version = "1.8.4" description = "Python implementation of JWT, JWE, JWS and JWK" authors = ["Roland Hedberg "] license = "Apache-2.0" From 322b5bcb5592b9f904ab498bb8d76fb1857a7348 Mon Sep 17 00:00:00 2001 From: roland Date: Fri, 4 Aug 2023 10:44:22 +0200 Subject: [PATCH 03/12] None not allowed here --- src/cryptojwt/jwt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index edb1dac..12e7905 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -265,6 +265,9 @@ def pack( else: _key = None + if jws_headers is None: + jws_headers = {} + _jws = JWS(self.message(signing_key=_key, **_args), alg=self.alg, **jws_headers) _sjwt = _jws.sign_compact([_key]) else: From 48338b4fcdf0bad426e064717a69efffa977857d Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Wed, 30 Aug 2023 08:43:33 +0200 Subject: [PATCH 04/12] Make it possible to wrap the payload in a message class dependent on the header parameter "typ". New method: add_keys() --- src/cryptojwt/jwk/ec.py | 2 +- src/cryptojwt/jwt.py | 71 +++++++++++++++++++------------------ src/cryptojwt/key_bundle.py | 2 +- src/cryptojwt/key_jar.py | 6 ++++ 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/cryptojwt/jwk/ec.py b/src/cryptojwt/jwk/ec.py index f1bc61e..4acf9d9 100644 --- a/src/cryptojwt/jwk/ec.py +++ b/src/cryptojwt/jwk/ec.py @@ -55,7 +55,7 @@ def ec_construct_public(num): def ec_construct_private(num): """ - Given a set of values on public and private attributes build a elliptic + Given a set of values on public and private attributes build an elliptic curve private key instance. :param num: A dictionary with public and private attributes and their values diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index 12e7905..8adabc3 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -1,11 +1,13 @@ """Basic JSON Web Token implementation.""" import json +from json import JSONDecodeError import logging import time -import uuid -from json import JSONDecodeError from typing import Dict +from typing import List +from typing import MutableMapping from typing import Optional +import uuid from .exception import HeaderError from .exception import VerificationError @@ -79,23 +81,24 @@ class JWT: """The basic JSON Web Token class.""" def __init__( - self, - key_jar=None, - iss="", - lifetime=0, - sign=True, - sign_alg="RS256", - encrypt=False, - enc_enc="A128GCM", - enc_alg="RSA-OAEP-256", - msg_cls=None, - iss2msg_cls=None, - skew=15, - allowed_sign_algs=None, - allowed_enc_algs=None, - allowed_enc_encs=None, - allowed_max_lifetime=None, - zip="", + self, + key_jar=None, + iss: str="", + lifetime: int = 0, + sign: bool = True, + sign_alg: str = "RS256", + encrypt: bool = False, + enc_enc: str = "A128GCM", + enc_alg: str = "RSA-OAEP-256", + msg_cls: MutableMapping = None, + iss2msg_cls: Dict[str, str] = None, + skew: int = 15, + allowed_sign_algs: List[str] = None, + allowed_enc_algs: List[str] = None, + allowed_enc_encs: List[str] = None, + allowed_max_lifetime: int = None, + zip: str = "", + typ2msg_cls: Dict[str, str] = None ): self.key_jar = key_jar # KeyJar instance self.iss = iss # My identifier @@ -212,15 +215,15 @@ def message(self, signing_key, **kwargs): return json.dumps(kwargs) def pack( - self, - payload: Optional[dict] = None, - kid: Optional[str] = "", - issuer_id: Optional[str] = "", - recv: Optional[str] = "", - aud: Optional[str] = None, - iat: Optional[int] = None, - jws_headers: Dict[str, str] = None, - **kwargs + self, + payload: Optional[dict] = None, + kid: Optional[str] = "", + issuer_id: Optional[str] = "", + recv: Optional[str] = "", + aud: Optional[str] = None, + iat: Optional[int] = None, + jws_headers: Dict[str, str] = None, + **kwargs ) -> str: """ @@ -319,8 +322,7 @@ def verify_profile(msg_cls, info, **kwargs): :return: The verified message as a msg_cls instance. """ _msg = msg_cls(**info) - if not _msg.verify(**kwargs): - raise VerificationError() + _msg.verify(**kwargs) return _msg def unpack(self, token, timestamp=None): @@ -392,11 +394,10 @@ def unpack(self, token, timestamp=None): if self.msg_cls: _msg_cls = self.msg_cls else: - try: - # try to find a issuer specific message class - _msg_cls = self.iss2msg_cls[_info["iss"]] - except KeyError: - _msg_cls = None + # try to find an issuer specific message class + _msg_cls = self.iss2msg_cls.get(_info["iss"]) + if not _msg_cls: + _msg_cls = self.typ2msg_cls.get(_jws_header['typ']) timestamp = timestamp or utc_time_sans_frac() diff --git a/src/cryptojwt/key_bundle.py b/src/cryptojwt/key_bundle.py index 451503c..245b053 100755 --- a/src/cryptojwt/key_bundle.py +++ b/src/cryptojwt/key_bundle.py @@ -681,7 +681,7 @@ def append(self, key): @keys_writer def extend(self, keys): - """Add a key to the list of keys.""" + """Add a list of keys to the list of keys.""" self._keys.extend(keys) @keys_writer diff --git a/src/cryptojwt/key_jar.py b/src/cryptojwt/key_jar.py index c9ab1bb..92ecdec 100755 --- a/src/cryptojwt/key_jar.py +++ b/src/cryptojwt/key_jar.py @@ -3,6 +3,7 @@ from typing import List from typing import Optional +from cryptojwt.jwk import JWK from requests import request from .exception import IssuerNotFound @@ -161,6 +162,11 @@ def add_kb(self, issuer_id, kb): issuer.add_kb(kb) self._issuers[issuer_id] = issuer + def add_keys(self, issuer_id: str, keys: List[JWK], **kwargs): + _kb = KeyBundle(**kwargs) + _kb.extend(keys) + self.add_kb(issuer_id, _kb) + @deprecated_alias(issuer="issuer_id", owner="issuer_id") def get(self, key_use, key_type="", issuer_id="", kid=None, **kwargs): """ From 76674049c216dca165eb2e3526bf55f396ac8b5e Mon Sep 17 00:00:00 2001 From: roland Date: Sat, 2 Sep 2023 13:55:42 +0200 Subject: [PATCH 05/12] Added trust_chain as allowed JWS header argument. --- src/cryptojwt/jws/jws.py | 5 +++-- src/cryptojwt/jwx.py | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cryptojwt/jws/jws.py b/src/cryptojwt/jws/jws.py index 7da26fa..b026bcb 100644 --- a/src/cryptojwt/jws/jws.py +++ b/src/cryptojwt/jws/jws.py @@ -118,8 +118,9 @@ def sign_compact(self, keys=None, protected=None, **kwargs): key, xargs, _alg = self.alg_keys(keys, "sig", protected) - if "typ" in self: - xargs["typ"] = self["typ"] + for param in ['typ', "trust_chain"]: + if param in self: + xargs[param] = self[param] _headers.update(xargs) jwt = JWSig(**_headers) diff --git a/src/cryptojwt/jwx.py b/src/cryptojwt/jwx.py index b941a49..4c16c5a 100644 --- a/src/cryptojwt/jwx.py +++ b/src/cryptojwt/jwx.py @@ -4,15 +4,14 @@ import warnings import requests - from cryptojwt.jwk import JWK from cryptojwt.key_bundle import KeyBundle from .exception import HeaderError from .jwe import DEPRECATED from .jwk.jwk import key_from_jwk_dict -from .jwk.rsa import RSAKey from .jwk.rsa import import_rsa_key +from .jwk.rsa import RSAKey from .jwk.x509 import load_x509_cert from .utils import as_bytes from .utils import as_unicode @@ -50,7 +49,7 @@ class JWx: :return: A class instance """ - args = ["alg", "jku", "jwk", "x5u", "x5t", "x5c", "kid", "typ", "cty", "crit"] + args = ["alg", "jku", "jwk", "x5u", "x5t", "x5c", "kid", "typ", "cty", "crit", "trust_chain"] def __init__(self, msg=None, with_digest=False, httpc=None, **kwargs): self.msg = msg From 5cc042fb91aad3e741a177a35d9d5420e8301f7b Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Mon, 4 Sep 2023 08:37:53 +0200 Subject: [PATCH 06/12] Make it possible to wrap the payload in a message class dependent on the header parameter "typ". --- src/cryptojwt/jwt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index 8adabc3..a47c789 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -112,6 +112,7 @@ def __init__( self.with_jti = False # If a jti should be added # A map between issuers and the message classes they use self.iss2msg_cls = iss2msg_cls or {} + self.typ2msg_cls = typ2msg_cls or {} # Allowed time skew self.skew = skew # When verifying/decrypting @@ -396,7 +397,7 @@ def unpack(self, token, timestamp=None): else: # try to find an issuer specific message class _msg_cls = self.iss2msg_cls.get(_info["iss"]) - if not _msg_cls: + if not _msg_cls and 'typ' in _jws_header: _msg_cls = self.typ2msg_cls.get(_jws_header['typ']) timestamp = timestamp or utc_time_sans_frac() From 1682a5f26eb468224eb12d51c317c9bc9bad1565 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 9 Sep 2023 17:27:16 +0200 Subject: [PATCH 07/12] More robust. --- src/cryptojwt/jwt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index a47c789..04b4e7c 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -395,9 +395,11 @@ def unpack(self, token, timestamp=None): if self.msg_cls: _msg_cls = self.msg_cls else: + _msg_cls = None # try to find an issuer specific message class - _msg_cls = self.iss2msg_cls.get(_info["iss"]) - if not _msg_cls and 'typ' in _jws_header: + if "iss" in _info: + _msg_cls = self.iss2msg_cls.get(_info["iss"]) + if not _msg_cls and _jws_header and 'typ' in _jws_header: _msg_cls = self.typ2msg_cls.get(_jws_header['typ']) timestamp = timestamp or utc_time_sans_frac() From 6234cc297dd2daf7c37671e53182a3d71f5921e7 Mon Sep 17 00:00:00 2001 From: roland Date: Sat, 23 Sep 2023 08:31:18 +0200 Subject: [PATCH 08/12] Added jwk and x5c as allowed JWS header argument. --- src/cryptojwt/jws/jws.py | 2 +- src/cryptojwt/jwt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cryptojwt/jws/jws.py b/src/cryptojwt/jws/jws.py index b026bcb..6ed1646 100644 --- a/src/cryptojwt/jws/jws.py +++ b/src/cryptojwt/jws/jws.py @@ -118,7 +118,7 @@ def sign_compact(self, keys=None, protected=None, **kwargs): key, xargs, _alg = self.alg_keys(keys, "sig", protected) - for param in ['typ', "trust_chain"]: + for param in ['typ', "trust_chain", "jwk", "x5c"]: if param in self: xargs[param] = self[param] diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index 04b4e7c..c5c4dd9 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -90,7 +90,7 @@ def __init__( encrypt: bool = False, enc_enc: str = "A128GCM", enc_alg: str = "RSA-OAEP-256", - msg_cls: MutableMapping = None, + msg_cls: Optional[MutableMapping] = None, iss2msg_cls: Dict[str, str] = None, skew: int = 15, allowed_sign_algs: List[str] = None, From f48aa2db103bab6b67dbcfdd8359c2d86b772463 Mon Sep 17 00:00:00 2001 From: roland Date: Thu, 28 Sep 2023 16:27:16 +0200 Subject: [PATCH 09/12] Remove specific header extensions. --- src/cryptojwt/jws/jws.py | 2 +- src/cryptojwt/jwt.py | 6 +++--- tests/test_09_jwt.py | 41 ++++++++++++++++++++-------------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/cryptojwt/jws/jws.py b/src/cryptojwt/jws/jws.py index 6ed1646..fd12c26 100644 --- a/src/cryptojwt/jws/jws.py +++ b/src/cryptojwt/jws/jws.py @@ -118,7 +118,7 @@ def sign_compact(self, keys=None, protected=None, **kwargs): key, xargs, _alg = self.alg_keys(keys, "sig", protected) - for param in ['typ', "trust_chain", "jwk", "x5c"]: + for param in ['typ']: if param in self: xargs[param] = self[param] diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index c5c4dd9..5e22750 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -98,7 +98,7 @@ def __init__( allowed_enc_encs: List[str] = None, allowed_max_lifetime: int = None, zip: str = "", - typ2msg_cls: Dict[str, str] = None + typ2msg_cls: Dict = None ): self.key_jar = key_jar # KeyJar instance self.iss = iss # My identifier @@ -272,8 +272,8 @@ def pack( if jws_headers is None: jws_headers = {} - _jws = JWS(self.message(signing_key=_key, **_args), alg=self.alg, **jws_headers) - _sjwt = _jws.sign_compact([_key]) + _jws = JWS(self.message(signing_key=_key, **_args), alg=self.alg) + _sjwt = _jws.sign_compact([_key], protected=jws_headers) else: _sjwt = self.message(signing_key=None, **_args) diff --git a/tests/test_09_jwt.py b/tests/test_09_jwt.py index 0bb912f..bb413f0 100755 --- a/tests/test_09_jwt.py +++ b/tests/test_09_jwt.py @@ -1,16 +1,14 @@ import os import pytest - -from cryptojwt.exception import IssuerNotFound from cryptojwt.jws.exception import NoSuitableSigningKeys from cryptojwt.jwt import JWT -from cryptojwt.jwt import VerificationError from cryptojwt.jwt import pick_key from cryptojwt.jwt import utc_time_sans_frac +from cryptojwt.jwt import VerificationError from cryptojwt.key_bundle import KeyBundle -from cryptojwt.key_jar import KeyJar from cryptojwt.key_jar import init_key_jar +from cryptojwt.key_jar import KeyJar __author__ = "Roland Hedberg" @@ -136,19 +134,6 @@ def test_jwt_pack_and_unpack_max_lifetime_exceeded(): _ = bob.unpack(_jwt) -def test_jwt_pack_and_unpack_max_lifetime_exceeded(): - lifetime = 3600 - alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS256", lifetime=lifetime) - payload = {"sub": "sub"} - _jwt = alice.pack(payload=payload) - - bob = JWT( - key_jar=BOB_KEY_JAR, iss=BOB, allowed_sign_algs=["RS256"], allowed_max_lifetime=lifetime - 1 - ) - with pytest.raises(VerificationError): - _ = bob.unpack(_jwt) - - def test_jwt_pack_and_unpack_timestamp(): lifetime = 3600 alice = JWT(key_jar=ALICE_KEY_JAR, iss=ALICE, sign_alg="RS256", lifetime=lifetime) @@ -255,9 +240,11 @@ def test_with_jti(): class DummyMsg(object): + def __init__(self, **kwargs): for key, val in kwargs.items(): setattr(self, key, val) + self.jws_headers = {} def verify(self, **kwargs): return True @@ -322,12 +309,26 @@ def test_eddsa_jwt(): ] } JWT_TEST = ( - "eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0." - + "eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9." - + "rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg" + "eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0." + + "eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9." + + "rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg" ) ISSUER = "https://idsvr.example.com" kj = KeyJar() kj.add_kb(ISSUER, KeyBundle(JWKS_DICT)) jwt = JWT(key_jar=kj) _ = jwt.unpack(JWT_TEST, timestamp=1655278809) + + +def test_extra_headers(): + _kj = KeyJar() + _kj.add_symmetric(ALICE, "hemligt ordsprak", usage=["sig"]) + + alice = JWT(key_jar=_kj, iss=ALICE, sign_alg="HS256") + payload = {"sub": "sub2"} + _jwt = alice.pack(payload=payload, jws_headers={"xtra": "header", "typ": "dummy"}) + + bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256", typ2msg_cls={"dummy": DummyMsg}) + info = bob.unpack(_jwt) + assert isinstance(info, DummyMsg) + assert set(info.jws_headers.keys()) == {'xtra', 'typ', 'alg', 'kid'} From d3b43019b1cb1be01fd0173a45d8ed26b7bfd94c Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 14 Oct 2023 10:49:41 +0200 Subject: [PATCH 10/12] Spelling error --- tests/test_09_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_09_jwt.py b/tests/test_09_jwt.py index bb413f0..66bf850 100755 --- a/tests/test_09_jwt.py +++ b/tests/test_09_jwt.py @@ -331,4 +331,4 @@ def test_extra_headers(): bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256", typ2msg_cls={"dummy": DummyMsg}) info = bob.unpack(_jwt) assert isinstance(info, DummyMsg) - assert set(info.jws_headers.keys()) == {'xtra', 'typ', 'alg', 'kid'} + assert set(info.jws_header.keys()) == {'xtra', 'typ', 'alg', 'kid'} From ef5857c0153ea7fba929e20ff614bdf24b36dd6f Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 14 Oct 2023 10:57:39 +0200 Subject: [PATCH 11/12] Updated according to proposals from @peppelinux and @jschlyter . --- src/cryptojwt/jwt.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index 5e22750..3802c08 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -91,14 +91,14 @@ def __init__( enc_enc: str = "A128GCM", enc_alg: str = "RSA-OAEP-256", msg_cls: Optional[MutableMapping] = None, - iss2msg_cls: Dict[str, str] = None, - skew: int = 15, - allowed_sign_algs: List[str] = None, - allowed_enc_algs: List[str] = None, - allowed_enc_encs: List[str] = None, - allowed_max_lifetime: int = None, - zip: str = "", - typ2msg_cls: Dict = None + iss2msg_cls: Optional[Dict[str, str]] = None, + skew: Optional[int] = 15, + allowed_sign_algs: Optional[List[str]] = None, + allowed_enc_algs: Optional[List[str]] = None, + allowed_enc_encs: Optional[List[str]] = None, + allowed_max_lifetime: Optional[int] = None, + zip: Optional[str] = "", + typ2msg_cls: Optional[Dict] = None ): self.key_jar = key_jar # KeyJar instance self.iss = iss # My identifier @@ -223,7 +223,7 @@ def pack( recv: Optional[str] = "", aud: Optional[str] = None, iat: Optional[int] = None, - jws_headers: Dict[str, str] = None, + jws_headers: Optional[Dict[str, str]] = None, **kwargs ) -> str: """ @@ -269,8 +269,7 @@ def pack( else: _key = None - if jws_headers is None: - jws_headers = {} + jws_headers = jws_headers or {} _jws = JWS(self.message(signing_key=_key, **_args), alg=self.alg) _sjwt = _jws.sign_compact([_key], protected=jws_headers) From a24f6d9f8e025c4cc42ed61dc7705c3c89f141d0 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 14 Oct 2023 11:09:39 +0200 Subject: [PATCH 12/12] Ran black and isort --- src/cryptojwt/jwe/jwe_ec.py | 1 - src/cryptojwt/jwk/__init__.py | 1 - src/cryptojwt/jwk/okp.py | 1 - src/cryptojwt/jws/jws.py | 2 +- src/cryptojwt/jwt.py | 62 +++++++++++++++++------------------ src/cryptojwt/jwx.py | 3 +- src/cryptojwt/key_bundle.py | 1 - src/cryptojwt/key_jar.py | 4 +-- tests/test_09_jwt.py | 14 ++++---- 9 files changed, 43 insertions(+), 46 deletions(-) diff --git a/src/cryptojwt/jwe/jwe_ec.py b/src/cryptojwt/jwe/jwe_ec.py index 3321a8e..b8c9bc6 100644 --- a/src/cryptojwt/jwe/jwe_ec.py +++ b/src/cryptojwt/jwe/jwe_ec.py @@ -213,7 +213,6 @@ def encrypt(self, key=None, iv="", cek="", **kwargs): return jwe.pack(parts=[iv, ctxt, tag]) def decrypt(self, token=None, **kwargs): - if isinstance(token, JWEnc): jwe = token else: diff --git a/src/cryptojwt/jwk/__init__.py b/src/cryptojwt/jwk/__init__.py index d8fd480..fe4e9cc 100644 --- a/src/cryptojwt/jwk/__init__.py +++ b/src/cryptojwt/jwk/__init__.py @@ -31,7 +31,6 @@ class JWK(object): def __init__( self, kty="", alg="", use="", kid="", x5c=None, x5t="", x5u="", key_ops=None, **kwargs ): - self.extra_args = kwargs # want kty, alg, use and kid to be strings diff --git a/src/cryptojwt/jwk/okp.py b/src/cryptojwt/jwk/okp.py index 7d165f7..8315962 100644 --- a/src/cryptojwt/jwk/okp.py +++ b/src/cryptojwt/jwk/okp.py @@ -321,7 +321,6 @@ def cmp_keys(a, b, key_type): def new_okp_key(crv, kid="", **kwargs): - _key = OKP_CRV2PRIVATE[crv].generate() _rk = OKPKey(priv_key=_key, kid=kid, **kwargs) diff --git a/src/cryptojwt/jws/jws.py b/src/cryptojwt/jws/jws.py index fd12c26..f521cbc 100644 --- a/src/cryptojwt/jws/jws.py +++ b/src/cryptojwt/jws/jws.py @@ -118,7 +118,7 @@ def sign_compact(self, keys=None, protected=None, **kwargs): key, xargs, _alg = self.alg_keys(keys, "sig", protected) - for param in ['typ']: + for param in ["typ"]: if param in self: xargs[param] = self[param] diff --git a/src/cryptojwt/jwt.py b/src/cryptojwt/jwt.py index 3802c08..0146934 100755 --- a/src/cryptojwt/jwt.py +++ b/src/cryptojwt/jwt.py @@ -1,13 +1,13 @@ """Basic JSON Web Token implementation.""" import json -from json import JSONDecodeError import logging import time +import uuid +from json import JSONDecodeError from typing import Dict from typing import List from typing import MutableMapping from typing import Optional -import uuid from .exception import HeaderError from .exception import VerificationError @@ -81,24 +81,24 @@ class JWT: """The basic JSON Web Token class.""" def __init__( - self, - key_jar=None, - iss: str="", - lifetime: int = 0, - sign: bool = True, - sign_alg: str = "RS256", - encrypt: bool = False, - enc_enc: str = "A128GCM", - enc_alg: str = "RSA-OAEP-256", - msg_cls: Optional[MutableMapping] = None, - iss2msg_cls: Optional[Dict[str, str]] = None, - skew: Optional[int] = 15, - allowed_sign_algs: Optional[List[str]] = None, - allowed_enc_algs: Optional[List[str]] = None, - allowed_enc_encs: Optional[List[str]] = None, - allowed_max_lifetime: Optional[int] = None, - zip: Optional[str] = "", - typ2msg_cls: Optional[Dict] = None + self, + key_jar=None, + iss: str = "", + lifetime: int = 0, + sign: bool = True, + sign_alg: str = "RS256", + encrypt: bool = False, + enc_enc: str = "A128GCM", + enc_alg: str = "RSA-OAEP-256", + msg_cls: Optional[MutableMapping] = None, + iss2msg_cls: Optional[Dict[str, str]] = None, + skew: Optional[int] = 15, + allowed_sign_algs: Optional[List[str]] = None, + allowed_enc_algs: Optional[List[str]] = None, + allowed_enc_encs: Optional[List[str]] = None, + allowed_max_lifetime: Optional[int] = None, + zip: Optional[str] = "", + typ2msg_cls: Optional[Dict] = None, ): self.key_jar = key_jar # KeyJar instance self.iss = iss # My identifier @@ -216,15 +216,15 @@ def message(self, signing_key, **kwargs): return json.dumps(kwargs) def pack( - self, - payload: Optional[dict] = None, - kid: Optional[str] = "", - issuer_id: Optional[str] = "", - recv: Optional[str] = "", - aud: Optional[str] = None, - iat: Optional[int] = None, - jws_headers: Optional[Dict[str, str]] = None, - **kwargs + self, + payload: Optional[dict] = None, + kid: Optional[str] = "", + issuer_id: Optional[str] = "", + recv: Optional[str] = "", + aud: Optional[str] = None, + iat: Optional[int] = None, + jws_headers: Optional[Dict[str, str]] = None, + **kwargs ) -> str: """ @@ -398,8 +398,8 @@ def unpack(self, token, timestamp=None): # try to find an issuer specific message class if "iss" in _info: _msg_cls = self.iss2msg_cls.get(_info["iss"]) - if not _msg_cls and _jws_header and 'typ' in _jws_header: - _msg_cls = self.typ2msg_cls.get(_jws_header['typ']) + if not _msg_cls and _jws_header and "typ" in _jws_header: + _msg_cls = self.typ2msg_cls.get(_jws_header["typ"]) timestamp = timestamp or utc_time_sans_frac() diff --git a/src/cryptojwt/jwx.py b/src/cryptojwt/jwx.py index 4c16c5a..52c696b 100644 --- a/src/cryptojwt/jwx.py +++ b/src/cryptojwt/jwx.py @@ -4,14 +4,15 @@ import warnings import requests + from cryptojwt.jwk import JWK from cryptojwt.key_bundle import KeyBundle from .exception import HeaderError from .jwe import DEPRECATED from .jwk.jwk import key_from_jwk_dict -from .jwk.rsa import import_rsa_key from .jwk.rsa import RSAKey +from .jwk.rsa import import_rsa_key from .jwk.x509 import load_x509_cert from .utils import as_bytes from .utils import as_unicode diff --git a/src/cryptojwt/key_bundle.py b/src/cryptojwt/key_bundle.py index 245b053..0fde736 100755 --- a/src/cryptojwt/key_bundle.py +++ b/src/cryptojwt/key_bundle.py @@ -566,7 +566,6 @@ def update(self): :return: True if update was ok or False if we encountered an error during update. """ if self.source: - try: if self.local: if self.fileformat in ["jwks", "jwk"]: diff --git a/src/cryptojwt/key_jar.py b/src/cryptojwt/key_jar.py index 92ecdec..f3716b0 100755 --- a/src/cryptojwt/key_jar.py +++ b/src/cryptojwt/key_jar.py @@ -3,9 +3,10 @@ from typing import List from typing import Optional -from cryptojwt.jwk import JWK from requests import request +from cryptojwt.jwk import JWK + from .exception import IssuerNotFound from .jwe.jwe import alg2keytype as jwe_alg2keytype from .jws.utils import alg2keytype as jws_alg2keytype @@ -481,7 +482,6 @@ def _add_key( no_kid_issuer=None, allow_missing_kid=False, ): - _issuer = self._get_issuer(issuer_id) if _issuer is None: logger.error('Issuer "{}" not in keyjar'.format(issuer_id)) diff --git a/tests/test_09_jwt.py b/tests/test_09_jwt.py index 66bf850..c8ec65c 100755 --- a/tests/test_09_jwt.py +++ b/tests/test_09_jwt.py @@ -1,14 +1,15 @@ import os import pytest + from cryptojwt.jws.exception import NoSuitableSigningKeys from cryptojwt.jwt import JWT +from cryptojwt.jwt import VerificationError from cryptojwt.jwt import pick_key from cryptojwt.jwt import utc_time_sans_frac -from cryptojwt.jwt import VerificationError from cryptojwt.key_bundle import KeyBundle -from cryptojwt.key_jar import init_key_jar from cryptojwt.key_jar import KeyJar +from cryptojwt.key_jar import init_key_jar __author__ = "Roland Hedberg" @@ -240,7 +241,6 @@ def test_with_jti(): class DummyMsg(object): - def __init__(self, **kwargs): for key, val in kwargs.items(): setattr(self, key, val) @@ -309,9 +309,9 @@ def test_eddsa_jwt(): ] } JWT_TEST = ( - "eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0." - + "eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9." - + "rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg" + "eyJraWQiOiItMTkwOTU3MjI1NyIsImFsZyI6IkVkRFNBIn0." + + "eyJqdGkiOiIyMjkxNmYzYy05MDkzLTQ4MTMtODM5Ny1mMTBlNmI3MDRiNjgiLCJkZWxlZ2F0aW9uSWQiOiJiNGFlNDdhNy02MjVhLTQ2MzAtOTcyNy00NTc2NGE3MTJjY2UiLCJleHAiOjE2NTUyNzkxMDksIm5iZiI6MTY1NTI3ODgwOSwic2NvcGUiOiJyZWFkIG9wZW5pZCIsImlzcyI6Imh0dHBzOi8vaWRzdnIuZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VybmFtZSIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImlhdCI6MTY1NTI3ODgwOSwicHVycG9zZSI6ImFjY2Vzc190b2tlbiJ9." + + "rjeE8D_e4RYzgvpu-nOwwx7PWMiZyDZwkwO6RiHR5t8g4JqqVokUKQt-oST1s45wubacfeDSFogOrIhe3UHDAg" ) ISSUER = "https://idsvr.example.com" kj = KeyJar() @@ -331,4 +331,4 @@ def test_extra_headers(): bob = JWT(key_jar=_kj, iss=BOB, sign_alg="HS256", typ2msg_cls={"dummy": DummyMsg}) info = bob.unpack(_jwt) assert isinstance(info, DummyMsg) - assert set(info.jws_header.keys()) == {'xtra', 'typ', 'alg', 'kid'} + assert set(info.jws_header.keys()) == {"xtra", "typ", "alg", "kid"}