Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Presentation submission parser implementation and mdoc support #391

Merged
merged 86 commits into from
Mar 27, 2025
Merged
Changes from 1 commit
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
f2a67aa
fix: get date from cert itself
PascalDR Mar 7, 2025
f2901c8
feat: added utility
PascalDR Mar 7, 2025
6aa74a9
fix: docstring
PascalDR Mar 7, 2025
b159fb9
fix: name conversion
PascalDR Mar 7, 2025
b16ad71
feat: initial implementation of X509Hanlder
PascalDR Mar 7, 2025
b52d754
fix: added utility
PascalDR Mar 7, 2025
e5f11a2
fix: vaious fix
PascalDR Mar 7, 2025
74d0ad8
tests: adapted tests
PascalDR Mar 7, 2025
c50a6d1
fix: exp retrieval
PascalDR Mar 10, 2025
3728fe5
fix: cert handling
PascalDR Mar 10, 2025
e32af84
tests: added test for x509 handler
PascalDR Mar 10, 2025
3d93b29
fix: function parameter
PascalDR Mar 10, 2025
88cac52
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-python…
PascalDR Mar 11, 2025
f4073e6
feat: added specialized exception
PascalDR Mar 11, 2025
eb7d313
fix: added methods for validation
PascalDR Mar 11, 2025
aac9687
fix: check format in response handler to distinguish Mdoc or SDJWT
PascalDR Mar 11, 2025
b4b299e
fix: jwt exp
PascalDR Mar 11, 2025
065fa8d
feat: added initial mdoc support
PascalDR Mar 11, 2025
82eeb54
tests: fix error messages
PascalDR Mar 11, 2025
d8f41e6
Merge branch 'feat/x509_handler' of https://github.com/italia/eudi-wa…
PascalDR Mar 11, 2025
92fd9a9
fix: documents path
PascalDR Mar 11, 2025
507ae7a
fix: function name
PascalDR Mar 11, 2025
b824455
fix: only verify signature
PascalDR Mar 11, 2025
6d351c0
test: test mdoc too
PascalDR Mar 11, 2025
27832e8
fix: get chains from configuration
PascalDR Mar 13, 2025
8443070
tests: adapted tests
PascalDR Mar 13, 2025
3bae010
fix: fail if configuration is wrong
PascalDR Mar 13, 2025
30c2093
feat: added utility
PascalDR Mar 13, 2025
1720114
fix: convert to pem list
PascalDR Mar 13, 2025
cd58384
tests: added configuration failure tests
PascalDR Mar 13, 2025
269e26c
chore: added x509 handler configuration
PascalDR Mar 13, 2025
f2eb33b
tests: test x5c trust parameter
PascalDR Mar 13, 2025
70090d6
chore: updated example documentation
PascalDR Mar 13, 2025
33d6328
Merge branch 'feat/x509_handler' of https://github.com/italia/eudi-wa…
PascalDR Mar 13, 2025
ad87391
Merge branch 'dev' of https://github.com/italia/eudi-wallet-it-python…
PascalDR Mar 18, 2025
dc30f4b
chore: taken edits from PR 314
PascalDR Mar 18, 2025
5b8f80d
chore: moved configuration
PascalDR Mar 18, 2025
cd40909
feat: total refactoring of PresentationSubmission
PascalDR Mar 18, 2025
8d510dc
tests: total refactoring of tests for PresentationSubmissionHandler p…
PascalDR Mar 18, 2025
9097b70
feat: added specialized exception
PascalDR Mar 18, 2025
339e1aa
tests: added test
PascalDR Mar 18, 2025
c367082
Update example/satosa/pyeudiw_backend.yaml
PascalDR Mar 18, 2025
278cdff
Update example/satosa/pyeudiw_backend.yaml
PascalDR Mar 18, 2025
0d24f4c
Merge branch 'feat/mdoc_support' into feat/presentation_submission
PascalDR Mar 19, 2025
ce6633c
Merge branch 'feat/presentation_submission' of https://github.com/ita…
PascalDR Mar 19, 2025
f27c8ce
fix: example config
PascalDR Mar 19, 2025
ccc77a9
feat: refactoring to PresentationSubmissionHandler
PascalDR Mar 19, 2025
1b889b3
feat: initial support for PresentationSubmissionHandler in openid4vp …
PascalDR Mar 19, 2025
4318628
feat: incomplete implementation of PresentationSubmissionHandler
PascalDR Mar 19, 2025
aec2c86
tests: adapted tests
PascalDR Mar 19, 2025
f652219
fix: variable name
PascalDR Mar 20, 2025
a5c2980
fix: variable name
PascalDR Mar 20, 2025
9615db8
Update pyeudiw/tests/settings.py
PascalDR Mar 20, 2025
fd3ea11
Merge branch 'feat/presentation_submission' of https://github.com/ita…
PascalDR Mar 20, 2025
023b98a
fix: unused import
PascalDR Mar 20, 2025
93b0632
feat: refactoring
PascalDR Mar 20, 2025
ac14693
fix: removed unused import
PascalDR Mar 20, 2025
1385090
fix: hadnle a list of credentials
PascalDR Mar 20, 2025
64bc569
tests: adapted tests
PascalDR Mar 20, 2025
718e765
fix: typo
PascalDR Mar 20, 2025
f98d277
chore: added dummy function
PascalDR Mar 20, 2025
257a069
fix: CI
PascalDR Mar 20, 2025
5991a31
fix: return diclosures
PascalDR Mar 21, 2025
a8e7c0c
Update pyeudiw/openid4vp/presentation_submission/__init__.py
PascalDR Mar 21, 2025
5d5a4b8
Merge branch 'feat/presentation_submission' of https://github.com/ita…
PascalDR Mar 21, 2025
667b9f8
feat: implemented expiration check
PascalDR Mar 21, 2025
97c4eca
chore: moved unused materials
PascalDR Mar 21, 2025
e151b0f
feat: parse library refactoring
PascalDR Mar 21, 2025
d02c604
fix: renamed class
PascalDR Mar 21, 2025
0e1491f
feat: added methods to handle trust materials
PascalDR Mar 21, 2025
e00296c
feat: added trust anchor loader
PascalDR Mar 24, 2025
4b66451
feat: proper anchor loading
PascalDR Mar 24, 2025
4efcc68
feat: implemented loader
PascalDR Mar 24, 2025
de64dba
fix: various minor fix
PascalDR Mar 24, 2025
6de5079
fix: minor issues
PascalDR Mar 24, 2025
7f4c988
fix: handle JWK type too
PascalDR Mar 25, 2025
9d6318b
chore: removed unused import
PascalDR Mar 25, 2025
93da1b1
tests: test for x5c
PascalDR Mar 25, 2025
bce817c
feat: validate mdoc cbor x5c
PascalDR Mar 25, 2025
b59d8f2
tests: adapted tests
PascalDR Mar 25, 2025
3df0baf
fix: better error handling
PascalDR Mar 25, 2025
5a567e0
fix: example config
PascalDR Mar 25, 2025
1f7c46f
tests: fixed test
PascalDR Mar 25, 2025
61b93fd
fix: try catch
PascalDR Mar 25, 2025
f888c3f
tests: added test
PascalDR Mar 25, 2025
74bd5eb
Apply suggestions from code review
peppelinux Mar 27, 2025
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
Prev Previous commit
Next Next commit
feat: added initial mdoc support
PascalDR committed Mar 11, 2025
commit 065fa8d1c7346bdc7838f4d2043ad9d0b5067c75
194 changes: 115 additions & 79 deletions pyeudiw/satosa/default/response_handler.py
Original file line number Diff line number Diff line change
@@ -41,6 +41,9 @@
from pyeudiw.trust.exceptions import InvalidJwkMetadataException
from pyeudiw.jwt.exceptions import JWSVerificationError
from pyeudiw.sd_jwt.exceptions import UnsupportedSdAlg, InvalidKeyBinding
from pyeudiw.sd_jwt.schema import is_sd_jwt_kb_format
from pyeudiw.openid4vp.vp_mdoc_cbor import VpMDocCbor
from pyeudiw.openid4vp.exceptions import MdocCborValidationError, VPExpired



@@ -49,11 +52,11 @@ class ResponseHandler(ResponseHandlerInterface):
_SUPPORTED_RESPONSE_CONTENT_TYPE = "application/x-www-form-urlencoded"
_ACCEPTED_ISSUER_METADATA_TYPE = "openid_credential_issuer"

def _extract_all_user_attributes(self, attributes_by_issuers: dict) -> dict:
def _extract_all_user_attributes(self, extracted_attributess: dict) -> dict:
# for all the valid credentials, take the payload and the disclosure and disclose user attributes
# returns the user attributes ...
all_user_attributes = dict()
for i in attributes_by_issuers.values():
for i in extracted_attributess.values():
all_user_attributes.update(**i)
return all_user_attributes

@@ -158,7 +161,7 @@ def response_endpoint(
# (1) we don't check that presentation submission matches definition (yet)
# (2) we don't check that vp tokens are aligned with information declared in the presentation submission
# (3) we use all disclosed claims in vp tokens to build the user identity
attributes_by_issuer: dict[str, dict[str, Any]] = {}
extracted_attributes: dict[str, dict[str, Any]] = {}
credential_issuers: list[str] = []
encoded_vps: list[str] = (
[authz_payload.vp_token]
@@ -168,71 +171,115 @@ def response_endpoint(

for vp_token in encoded_vps:
# verify vp token and extract user information
try:
token_parser, token_verifier = self._vp_verifier_factory(
authz_payload.presentation_submission, vp_token, request_session
)
except NotKBJWT as e400:
return self._handle_400(
context,
"invalid vp token: not a key-bound jwt",
e400
)

try:
token_issuer = token_parser.get_issuer_name()
whitelisted_keys = self.trust_evaluator.get_public_keys(token_issuer)
token_verifier.verify_signature(whitelisted_keys)
token_verifier.verify_challenge()
except MissingIssuer as e400:
return self._handle_400(
context,
"invalid vp token: missing issuer information",
e400
)
except InvalidJwkMetadataException as e500:
return self._handle_500(
context,
"trust error: cannot fetch public keys",
e500
)
except JWSVerificationError as e400:
return self._handle_400(
context,
"invalid vp token: invalid signature",
e400
)
except UnsupportedSdAlg as e400:
return self._handle_400(
context,
"invalid vp token: unsupported signature algorithm",
e400
)
except InvalidKeyBinding as e400:
return self._handle_400(
context,
"invalid vp token: nonce or aud mismatch",
e400
)
except ValueError as e400:
return self._handle_400(
context,
"invalid vp token: missing or invalid iat claim",
e400
)
except Exception as e400:
return self._handle_400(
context,
"trust error: cannot verify vp token",
e400
)

claims = token_parser.get_credentials()
iss = token_parser.get_issuer_name()
attributes_by_issuer[iss] = claims
self._log_debug(context, f"disclosed claims {claims} from issuer {iss}")

all_attributes = self._extract_all_user_attributes(attributes_by_issuer)
if is_sd_jwt_kb_format(vp_token):
try:
challenge = self._get_verifier_challenge(request_session)

token_processor = VpVcSdJwtParserVerifier(
vp_token, challenge["aud"], challenge["nonce"]
)

token_issuer = token_processor.get_issuer_name()

whitelisted_keys = self.trust_evaluator.get_public_keys(token_issuer)
token_processor.verify_signature(whitelisted_keys)
token_processor.verify_challenge()

if token_processor.is_expired() == True:
raise VPExpired("VP is expired")

claims = token_processor.get_credentials()
iss = token_processor.get_issuer_name()

extracted_attributes[iss] = claims
except MissingIssuer as e400:
return self._handle_400(
context,
"invalid vp token: missing issuer information",
e400
)
except InvalidJwkMetadataException as e500:
return self._handle_500(
context,
"trust error: cannot fetch public keys",
e500
)
except JWSVerificationError as e400:
return self._handle_400(
context,
"invalid vp token: invalid signature",
e400
)
except UnsupportedSdAlg as e400:
return self._handle_400(
context,
"invalid vp token: unsupported signature algorithm",
e400
)
except InvalidKeyBinding as e400:
return self._handle_400(
context,
"invalid vp token: nonce or aud mismatch",
e400
)
except ValueError as e400:
return self._handle_400(
context,
"invalid vp token: missing or invalid iat claim",
e400
)
except VPExpired as e400:
return self._handle_400(
context,
"invalid vp token: expired",
e400
)
except Exception as e400:
breakpoint()
return self._handle_400(
context,
"trust error: cannot verify vp token",
e400
)
else:
try:
token_processor = VpMDocCbor(vp_token)
except Exception as e400:
return self._handle_400(
context,
"invalid vp token: cannot parse vp token",
e400
)

try:
token_processor.verify_signature()
if token_processor.is_expired() == True:
raise VPExpired("VP is expired")

claims = token_processor.get_credentials()
doc_type = token_processor.get_doc_type()

extracted_attributes[doc_type] = claims
except MdocCborValidationError as e400:
return self._handle_400(
context,
"invalid vp token: invalid signature",
e400
)
except VPExpired as e400:
return self._handle_400(
context,
"invalid vp token: expired",
e400
)
except Exception as e400:
return self._handle_400(
context,
"trust error: cannot verify vp token",
e400
)

all_attributes = self._extract_all_user_attributes(extracted_attributes)
iss_list_serialized = ";".join(credential_issuers) # marshaling is whatever
internal_resp = self._translate_response(
all_attributes, iss_list_serialized, context
@@ -358,17 +405,6 @@ def _parse_authorization_response(
Exception("invalid program state"),
)

def _vp_verifier_factory(
self, presentation_submission: dict, token: str, session_data: dict
) -> tuple[VpTokenParser, VpTokenVerifier]:
# TODO: la funzione dovrebbe consumare la presentation submission per sapere quale token
# ritornare - per ora viene ritornata l'unica implementazione possibile
challenge = self._get_verifier_challenge(session_data)
token_processor = VpVcSdJwtParserVerifier(
token, challenge["aud"], challenge["nonce"]
)
return (token_processor, deepcopy(token_processor))

def _get_verifier_challenge(self, session_data: dict) -> VerifierChallenge:
# TODO: check aud according to the LSP Potential singularities ...
return {"aud": self.client_id, "nonce": session_data["nonce"]}