From 455515c6cc71798d019d6af261c119c0246199dc Mon Sep 17 00:00:00 2001 From: PascalDR Date: Fri, 26 Sep 2025 17:21:11 +0200 Subject: [PATCH 1/5] feat: is_jwt util --- pyeudiw/jwt/helper.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyeudiw/jwt/helper.py b/pyeudiw/jwt/helper.py index 51ffdd1fe..9da027b2c 100644 --- a/pyeudiw/jwt/helper.py +++ b/pyeudiw/jwt/helper.py @@ -1,3 +1,4 @@ +import re import json from typing import Literal, TypeAlias @@ -18,6 +19,8 @@ KeyLike: TypeAlias = ECKey | RSAKey | OKPKey | SYMKey SerializationFormat = Literal["compact", "json"] +JWT_REGEX = r'^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$' + class JWHelperInterface: def __init__(self, jwks: list[KeyLike | dict] | KeyLike | dict) -> None: @@ -120,6 +123,21 @@ def is_payload_expired(token_payload: dict) -> bool: return True return False +def is_jwt(token: str) -> bool: + """ + Check if a string is a JWT. + + :param token: The string to check. + :type token: str + + :returns: True if the string is a JWT, False otherwise. + :rtype: bool + """ + if not isinstance(token, str): + return False + if re.match(JWT_REGEX, token): + return True + return False def is_jwt_expired(token: str) -> bool: """ From 23ad981b8b6004969461833c9a97e037e2e00d85 Mon Sep 17 00:00:00 2001 From: PascalDR Date: Fri, 26 Sep 2025 17:22:53 +0200 Subject: [PATCH 2/5] fix: original jwt field --- pyeudiw/satosa/frontends/openid4vci/models/par_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyeudiw/satosa/frontends/openid4vci/models/par_request.py b/pyeudiw/satosa/frontends/openid4vci/models/par_request.py index d97472f26..f6da56e5b 100644 --- a/pyeudiw/satosa/frontends/openid4vci/models/par_request.py +++ b/pyeudiw/satosa/frontends/openid4vci/models/par_request.py @@ -131,6 +131,7 @@ class SignedParRequest(OpenId4VciBaseModel): redirect_uri: str = None jti: str = None issuer_state: str = None + jws: str = None @model_validator(mode='after') def check_par_request(self) -> "ParRequest": From 630d8df8902209d798b1b0b77184652c16c03c86 Mon Sep 17 00:00:00 2001 From: PascalDR Date: Fri, 26 Sep 2025 17:23:46 +0200 Subject: [PATCH 3/5] fix: enforce jwt parsing --- .../pushed_authorization_request_endpoint.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py index 907cb6c2e..8fef802ea 100644 --- a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py +++ b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py @@ -31,6 +31,8 @@ FORM_URLENCODED ) +from pyeudiw.jwt.helper import is_jwt + CLASS_NAME = "ParHandler.pushed_authorization_request_endpoint" class ParHandler(VCIBaseEndpoint): @@ -111,6 +113,13 @@ def endpoint(self, context: Context): if request and (self.signed_par_request == "true" or self.signed_par_request == "both"): try: + if not is_jwt(request): + self._log_error( + CLASS_NAME, + f"invalid request parameter for `par`, invalid JWS: {request}" + ) + return self._handle_400(context, "invalid request parameters") + payload = self.jws_helper.verify(request) if not isinstance(payload, dict): @@ -120,6 +129,8 @@ def endpoint(self, context: Context): ) return self._handle_400(context, "invalid request parameters") + payload["jws"] = request + par_request = SignedParRequest.model_validate( payload, context={ ENDPOINT_CTX: "par", From 8eda1c5acf0c8d56be61de3167fd24c4b9d4c7e4 Mon Sep 17 00:00:00 2001 From: Pasquale De Rose Date: Mon, 29 Sep 2025 12:54:00 +0200 Subject: [PATCH 4/5] Update pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py Co-authored-by: Giuseppe De Marco --- .../endpoints/pushed_authorization_request_endpoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py index 8fef802ea..cd46ed14f 100644 --- a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py +++ b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py @@ -111,7 +111,7 @@ def endpoint(self, context: Context): request = data.get("request", "").strip() - if request and (self.signed_par_request == "true" or self.signed_par_request == "both"): + if request self.signed_par_request in ("true", "both"): try: if not is_jwt(request): self._log_error( From cc82c79e9db85849133cdfb2b6268e57001b835a Mon Sep 17 00:00:00 2001 From: PascalDR Date: Mon, 29 Sep 2025 12:58:11 +0200 Subject: [PATCH 5/5] fix: syntax --- .../endpoints/pushed_authorization_request_endpoint.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py index cd46ed14f..c38f34d80 100644 --- a/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py +++ b/pyeudiw/satosa/frontends/openid4vci/endpoints/pushed_authorization_request_endpoint.py @@ -111,7 +111,7 @@ def endpoint(self, context: Context): request = data.get("request", "").strip() - if request self.signed_par_request in ("true", "both"): + if request and self.signed_par_request in ("true", "both"): try: if not is_jwt(request): self._log_error( @@ -145,7 +145,7 @@ def endpoint(self, context: Context): f"invalid request parameter for `par`, invalid JWS: {request}" ) return self._handle_400(context, "invalid request parameters") - elif (self.signed_par_request == "false" or self.signed_par_request == "both"): + elif self.signed_par_request in ("false", "both"): par_request = ParRequest.model_validate( data, context={ ENDPOINT_CTX: "par",