From 71ff1e2251a044ef804c5d843a8c05bdbfa9241f Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Wed, 22 Sep 2021 09:20:11 +0200 Subject: [PATCH 1/9] Update keyjar when needed. --- src/oidcmsg/message.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/src/oidcmsg/message.py b/src/oidcmsg/message.py index f4878b3..0884f94 100755 --- a/src/oidcmsg/message.py +++ b/src/oidcmsg/message.py @@ -468,6 +468,28 @@ def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0): _jws = JWS(self.to_json(lev), alg=algorithm) return _jws.sign_compact(key) + def _gather_keys(self, keyjar, jwt, header, **kwargs): + key = [] + + if keyjar: + _keys = keyjar.get_jwt_verify_keys(jwt, **kwargs) + if not _keys: + keyjar.update() + _keys = keyjar.get_jwt_verify_keys(jwt, **kwargs) + key.extend(_keys) + + if "alg" in header and header["alg"] != "none": + if not key: + if keyjar: + keyjar.update() + key = keyjar.get_jwt_verify_keys(jwt, **kwargs) + if not key: + raise MissingSigningKey("alg=%s" % header["alg"]) + else: + raise MissingSigningKey("alg=%s" % header["alg"]) + + return key + def from_jwt(self, txt, keyjar, verify=True, **kwargs): """ Given a signed and/or encrypted JWT, verify its correctness and then @@ -515,7 +537,6 @@ def from_jwt(self, txt, keyjar, verify=True, **kwargs): jso = _jwt.payload() _header = _jwt.headers - key = [] # if "sender" in kwargs: # key.extend(keyjar.get_verify_key(owner=kwargs["sender"])) @@ -524,21 +545,13 @@ def from_jwt(self, txt, keyjar, verify=True, **kwargs): if _header["alg"] == "none": pass elif verify: - if keyjar: - key.extend(keyjar.get_jwt_verify_keys(_jwt, **kwargs)) + key = self._gather_keys(keyjar, _jwt, _header, **kwargs) - if "alg" in _header and _header["alg"] != "none": - if not key: - raise MissingSigningKey("alg=%s" % _header["alg"]) + if not key: + raise MissingSigningKey("alg=%s" % _header["alg"]) logger.debug("Found signing key.") - try: - _verifier.verify_compact(txt, key) - except NoSuitableSigningKeys: - if keyjar: - keyjar.update() - key = keyjar.get_jwt_verify_keys(_jwt, **kwargs) - _verifier.verify_compact(txt, key) + _verifier.verify_compact(txt, key) self.jws_header = _jwt.headers else: From 711c799fc2499a10126a64df6ebd07091a4b783b Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sat, 25 Sep 2021 09:28:39 +0200 Subject: [PATCH 2/9] Sformat needed to be carried. --- src/oidcmsg/message.py | 22 ++++++++-------------- src/oidcmsg/oidc/__init__.py | 2 +- tests/test_06_oidc.py | 22 ++++++++++++++++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/oidcmsg/message.py b/src/oidcmsg/message.py index 0884f94..be343b8 100755 --- a/src/oidcmsg/message.py +++ b/src/oidcmsg/message.py @@ -314,10 +314,10 @@ def from_dict(self, dictionary, **kwargs): self._dict[key] = val continue - self._add_value(skey, vtyp, key, val, _deser, null_allowed) + self._add_value(skey, vtyp, key, val, _deser, null_allowed, sformat="dict") return self - def _add_value(self, skey, vtyp, key, val, _deser, null_allowed): + def _add_value(self, skey, vtyp, key, val, _deser, null_allowed, sformat="urlencoded"): """ Main method for adding a value to the instance. Does all the checking on type of value and if among allowed values. @@ -350,7 +350,7 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed): self._dict[skey] = [val] elif _deser: try: - self._dict[skey] = _deser(val, sformat="urlencoded") + self._dict[skey] = _deser(val, sformat=sformat) except Exception as exc: raise DecodeError(ERRTXT % (key, exc)) else: @@ -402,16 +402,6 @@ def _add_value(self, skey, vtyp, key, val, _deser, null_allowed): except Exception as exc: raise DecodeError(ERRTXT % (key, exc)) else: - # if isinstance(val, str): - # self._dict[skey] = val - # elif isinstance(val, list): - # if len(val) == 1: - # self._dict[skey] = val[0] - # elif not len(val): - # pass - # else: - # raise TooManyValues(key) - # else: self._dict[skey] = val elif vtyp is int: try: @@ -863,8 +853,12 @@ def add_non_standard(msg1, msg2): def list_serializer(vals, sformat="urlencoded", lev=0): - if isinstance(vals, str) or not isinstance(vals, list): + if isinstance(vals, str) and sformat == "dict": + return [vals] + + if not isinstance(vals, list): raise ValueError("Expected list: %s" % vals) + if sformat == "urlencoded": return " ".join(vals) else: diff --git a/src/oidcmsg/oidc/__init__.py b/src/oidcmsg/oidc/__init__.py index 8488469..5f09c76 100755 --- a/src/oidcmsg/oidc/__init__.py +++ b/src/oidcmsg/oidc/__init__.py @@ -797,7 +797,7 @@ def verify(self, **kwargs): # check that I'm among the recipients if kwargs["client_id"] not in self["aud"]: raise NotForMe( - "{} not in aud:{}".format(kwargs["client_id"], self["aud"]), self + '"{}" not in {}'.format(kwargs["client_id"], self["aud"]), self ) # Then azp has to be present and be one of the aud values diff --git a/tests/test_06_oidc.py b/tests/test_06_oidc.py index ca17c2a..bb118c6 100755 --- a/tests/test_06_oidc.py +++ b/tests/test_06_oidc.py @@ -6,7 +6,6 @@ from urllib.parse import parse_qs from urllib.parse import urlencode -import pytest from cryptojwt.exception import BadSignature from cryptojwt.exception import UnsupportedAlgorithm from cryptojwt.jws.exception import SignerAlgError @@ -14,6 +13,7 @@ from cryptojwt.jwt import JWT from cryptojwt.key_bundle import KeyBundle from cryptojwt.key_jar import KeyJar +import pytest from oidcmsg import proper_path from oidcmsg import time_util @@ -21,9 +21,8 @@ from oidcmsg.exception import MissingRequiredAttribute from oidcmsg.exception import NotAllowedValue from oidcmsg.exception import OidcMsgError -from oidcmsg.oauth2 import ResponseMessage from oidcmsg.oauth2 import ROPCAccessTokenRequest -from oidcmsg.oidc import JRD +from oidcmsg.oauth2 import ResponseMessage from oidcmsg.oidc import AccessTokenRequest from oidcmsg.oidc import AccessTokenResponse from oidcmsg.oidc import AddressClaim @@ -38,6 +37,7 @@ from oidcmsg.oidc import EXPError from oidcmsg.oidc import IATError from oidcmsg.oidc import IdToken +from oidcmsg.oidc import JRD from oidcmsg.oidc import Link from oidcmsg.oidc import OpenIDSchema from oidcmsg.oidc import ProviderConfigurationResponse @@ -661,7 +661,7 @@ def test_deserialize(self): "client_secret_expires_at": 1577858400, "registration_access_token": "this.is.an.access.token.value.ffx83", "registration_client_uri": "https://server.example.com/connect/register?client_id" - "=s6BhdRkqt3", + "=s6BhdRkqt3", "token_endpoint_auth_method": "client_secret_basic", "application_type": "web", "redirect_uris": [ @@ -1601,3 +1601,17 @@ def test_correct_sign_alg(): client_id="554295ce3770612820620000", allowed_sign_alg="HS256", ) + + +def test_ID_Token_space_in_id(): + idt = IdToken(**{ + "at_hash": "buCCujNN632UIV8-VbKhgw", + "sub": "user-subject-1234531", + "aud": "client_ifCttPphtLxtPWd20602 ^.+/", + "iss": "https://www.certification.openid.net/test/a/idpy/", + "exp": 1632495959, + "nonce": "B88En9UpdHkQZMQXK9U3KHzV", + "iat": 1632495659 + }) + + assert idt["aud"] == "client_ifCttPphtLxtPWd20602 ^.+/" From d2656c4be37ab9f58d7a03aa6b7640214eedb05b Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sun, 26 Sep 2021 09:03:15 +0200 Subject: [PATCH 3/9] list_deser should transform str into list of str. --- src/oidcmsg/message.py | 7 +++++-- tests/test_06_oidc.py | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/oidcmsg/message.py b/src/oidcmsg/message.py index be343b8..cf5e166 100755 --- a/src/oidcmsg/message.py +++ b/src/oidcmsg/message.py @@ -871,8 +871,11 @@ def list_deserializer(val, sformat="urlencoded"): return val.split(" ") elif isinstance(val, list) and len(val) == 1: return val[0].split(" ") - else: - return val + elif sformat == "dict": + if isinstance(val, str): + val = [val] + + return val def sp_sep_list_serializer(vals, sformat="urlencoded", lev=0): diff --git a/tests/test_06_oidc.py b/tests/test_06_oidc.py index bb118c6..7c31c9f 100755 --- a/tests/test_06_oidc.py +++ b/tests/test_06_oidc.py @@ -1614,4 +1614,11 @@ def test_ID_Token_space_in_id(): "iat": 1632495659 }) - assert idt["aud"] == "client_ifCttPphtLxtPWd20602 ^.+/" + assert idt["aud"] == ["client_ifCttPphtLxtPWd20602 ^.+/"] + + idt = IdToken(**{'at_hash': 'rgMbiR-Dj11dQjxhCyLkOw', 'sub': 'user-subject-1234531', + 'aud': 'client_dVCwIQuSKklinFP70742;#__$', + 'iss': 'https://www.certification.openid.net/test/a/idpy/', 'exp': 1632639462, + 'nonce': 'hUT3RhSooxC9CilrD8al6bGx', 'iat': 1632639162}) + + assert idt["aud"] == ["client_dVCwIQuSKklinFP70742;#__$"] From d803147a642073485d856827ffe8b17443fcdf02 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Sun, 26 Sep 2021 09:06:39 +0200 Subject: [PATCH 4/9] Bump version. --- src/oidcmsg/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidcmsg/__init__.py b/src/oidcmsg/__init__.py index 28cfc5f..1cefa2d 100755 --- a/src/oidcmsg/__init__.py +++ b/src/oidcmsg/__init__.py @@ -1,5 +1,5 @@ __author__ = "Roland Hedberg" -__version__ = "1.4.0" +__version__ = "1.4.1" import os from typing import Dict From 00792c321a83f8ff316937bcd4eb14910592dd92 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Tue, 5 Oct 2021 17:17:50 +0200 Subject: [PATCH 5/9] post_logout_redirect_uri is singleton. Fixed dump conversion. --- src/oidcmsg/impexp.py | 3 ++- src/oidcmsg/oidc/__init__.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/oidcmsg/impexp.py b/src/oidcmsg/impexp.py index 269916d..06435f0 100644 --- a/src/oidcmsg/impexp.py +++ b/src/oidcmsg/impexp.py @@ -2,6 +2,7 @@ from typing import List from typing import Optional +from cryptojwt import as_unicode from cryptojwt.utils import as_bytes from cryptojwt.utils import importer from cryptojwt.utils import qualified_name @@ -25,7 +26,7 @@ def __init__(self): def dump_attr(self, cls, item, exclude_attributes: Optional[List[str]] = None) -> dict: if cls in [None, 0, "", [], {}, bool, b'']: if cls == b'': - val = as_bytes(item) + val = as_unicode(item) else: val = item elif cls == "DICT_TYPE": diff --git a/src/oidcmsg/oidc/__init__.py b/src/oidcmsg/oidc/__init__.py index 5f09c76..95c5382 100755 --- a/src/oidcmsg/oidc/__init__.py +++ b/src/oidcmsg/oidc/__init__.py @@ -633,7 +633,7 @@ class RegistrationRequest(Message): # "client_id": SINGLE_OPTIONAL_STRING, # "client_secret": SINGLE_OPTIONAL_STRING, # "access_token": SINGLE_OPTIONAL_STRING, - "post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS, + "post_logout_redirect_uri": SINGLE_OPTIONAL_STRING, "frontchannel_logout_uri": SINGLE_OPTIONAL_STRING, "frontchannel_logout_session_required": SINGLE_OPTIONAL_BOOLEAN, "backchannel_logout_uri": SINGLE_OPTIONAL_STRING, From 04edd3591fb5d24a5fa14052ed86b5275f0e85e7 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Wed, 6 Oct 2021 10:07:22 +0200 Subject: [PATCH 6/9] post_logout_redirect_uris is a list. Fixed dump conversion. --- src/oidcmsg/oidc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidcmsg/oidc/__init__.py b/src/oidcmsg/oidc/__init__.py index 95c5382..5f09c76 100755 --- a/src/oidcmsg/oidc/__init__.py +++ b/src/oidcmsg/oidc/__init__.py @@ -633,7 +633,7 @@ class RegistrationRequest(Message): # "client_id": SINGLE_OPTIONAL_STRING, # "client_secret": SINGLE_OPTIONAL_STRING, # "access_token": SINGLE_OPTIONAL_STRING, - "post_logout_redirect_uri": SINGLE_OPTIONAL_STRING, + "post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS, "frontchannel_logout_uri": SINGLE_OPTIONAL_STRING, "frontchannel_logout_session_required": SINGLE_OPTIONAL_BOOLEAN, "backchannel_logout_uri": SINGLE_OPTIONAL_STRING, From 521bf2b5cb32cf9fd9b39ab7b138914eb6d76d22 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Wed, 6 Oct 2021 14:27:54 +0200 Subject: [PATCH 7/9] Right kind of token. --- src/oidcmsg/oidc/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidcmsg/oidc/session.py b/src/oidcmsg/oidc/session.py index 21b0676..3518b49 100644 --- a/src/oidcmsg/oidc/session.py +++ b/src/oidcmsg/oidc/session.py @@ -179,6 +179,6 @@ def verify(self, **kwargs): return False self[verified_claim_name("logout_token")] = idt - logger.info("Verified ID Token: {}".format(idt.to_dict())) + logger.info("Verified Logout Token: {}".format(idt.to_dict())) return True From 7c4c628e1b32d8b9110a0f0317345163f13311f6 Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Thu, 7 Oct 2021 09:45:42 +0200 Subject: [PATCH 8/9] Remove unused code. --- src/oidcmsg/oidc/__init__.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/oidcmsg/oidc/__init__.py b/src/oidcmsg/oidc/__init__.py index 5f09c76..c0b7193 100755 --- a/src/oidcmsg/oidc/__init__.py +++ b/src/oidcmsg/oidc/__init__.py @@ -771,14 +771,6 @@ def pack(self, alg="", **kwargs): else: self.pack_init() - # if 'jti' in self.c_param: - # try: - # _jti = kwargs['jti'] - # except KeyError: - # _jti = uuid.uuid4().hex - # - # self['jti'] = _jti - def to_jwt(self, key=None, algorithm="", lev=0, lifetime=0): self.pack(alg=algorithm, lifetime=lifetime) return Message.to_jwt(self, key=key, algorithm=algorithm, lev=lev) From 48186d680518b84a740dd0ad6528cf8236711f7f Mon Sep 17 00:00:00 2001 From: Roland Hedberg Date: Wed, 13 Oct 2021 12:21:27 +0200 Subject: [PATCH 9/9] post_logout_redirect_uri - singular. --- src/oidcmsg/oidc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oidcmsg/oidc/__init__.py b/src/oidcmsg/oidc/__init__.py index c0b7193..05b1877 100755 --- a/src/oidcmsg/oidc/__init__.py +++ b/src/oidcmsg/oidc/__init__.py @@ -633,7 +633,7 @@ class RegistrationRequest(Message): # "client_id": SINGLE_OPTIONAL_STRING, # "client_secret": SINGLE_OPTIONAL_STRING, # "access_token": SINGLE_OPTIONAL_STRING, - "post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS, + "post_logout_redirect_uri": SINGLE_OPTIONAL_STRING, "frontchannel_logout_uri": SINGLE_OPTIONAL_STRING, "frontchannel_logout_session_required": SINGLE_OPTIONAL_BOOLEAN, "backchannel_logout_uri": SINGLE_OPTIONAL_STRING,