Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pyeudiw/jwt/helper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
import json
from typing import Literal, TypeAlias

Expand All @@ -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-_]+$'
Copy link
Member

@peppelinux peppelinux Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure that it might work with an SD-JWT

Copy link
Collaborator Author

@PascalDR PascalDR Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case the signed request is always a simple JWS.
I can also add a proper function in the sd-jwt package if you want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this helper used also for SD-JWT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this moment this function is used exclusively to do the check for the signed request.



class JWHelperInterface:
def __init__(self, jwks: list[KeyLike | dict] | KeyLike | dict) -> None:
Expand Down Expand Up @@ -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:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
FORM_URLENCODED
)

from pyeudiw.jwt.helper import is_jwt

CLASS_NAME = "ParHandler.pushed_authorization_request_endpoint"

class ParHandler(VCIBaseEndpoint):
Expand Down Expand Up @@ -109,8 +111,15 @@ 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(
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):
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions pyeudiw/satosa/frontends/openid4vci/models/par_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down
Loading