1- import datetime
21import time
32import logging
43import inspect
4+ import base64
5+ import cbor2
6+
7+ from pycose .keys import CoseKey
8+ from pyeudiw .jwk import JWK
59from pyeudiw .satosa .schemas .credential_configurations import CredentialConfigurationsConfig
610from pyeudiw .storage .exceptions import EntryNotFound
711from abc import ABC , abstractmethod
@@ -157,17 +161,12 @@ def endpoint(self, context: Context) -> Response:
157161 if data .get ("credential_configuration_id" ):
158162 credential_configuration_id = data
159163 else : # validate credential_identifier with authorization_details of token
160- if auth_session .authorization_details :
161- for auth_details in auth_session .authorization_details :
162- if auth_details .credential_identifiers :
163- if credential_identifier in auth_details .credential_identifiers :
164- credential_configuration_id = "_" .join (credential_identifier .split ("_" )[:- 1 ])
165- break
166- else :
167- raise InvalidRequestException (
168- "credential_identifier not match with token authorization_details" )
164+ for auth_details in auth_session .authorization_details or []:
165+ if credential_identifier in (auth_details .credential_identifiers or []):
166+ credential_configuration_id = "_" .join (credential_identifier .split ("_" )[:- 1 ])
167+ break
169168 else :
170- raise InvalidRequestException ("Invalid credential_configuration_id" )
169+ raise InvalidRequestException ("Invalid credential_configuration_id or credential_identifier not match with token authorization_details " )
171170
172171 self .validate_request (context , entity )
173172
@@ -265,8 +264,7 @@ def _build_credential(
265264 match config .format :
266265 case CredentialConfigurationFormatEnum .SD_JWT .value :
267266 return self ._issue_sd_jwt (
268- user_entity , opendid4vci_entity , cred_config , config , holder_key = holder_key
269- )["issuance" ]
267+ user_entity , opendid4vci_entity , cred_config , config , holder_key = holder_key )
270268 case CredentialConfigurationFormatEnum .MSO_MDOC .value :
271269 return self ._issue_mso_mdoc (user_entity , opendid4vci_entity , cred_config , config , holder_key = holder_key )
272270 case _:
@@ -281,38 +279,67 @@ def _build_credential(
281279 def _issue_mso_mdoc (
282280 self ,
283281 user_entity : tuple [str , UserEntity ],
284- auth_session : AuthorizationSession , cred_config : CredentialConfigurationsConfig ,
285- config : CredentialConfiguration , holder_key : dict = None
282+ auth_session : AuthorizationSession , cred_config : CredentialConfigurationsConfig , config : CredentialConfiguration , holder_key : dict = None
286283 ) -> str :
287- credential : CredentialSpecificationConfig = cred_config .credential_specification [config .id ] #todo
284+ """References:
285+ - https://italia.github.io/eid-wallet-it-docs/releases/1.3.3/en/credential-data-model-pid.html#pid-data-model-in-mdoc-cbor-format
286+ - https://italia.github.io/eid-wallet-it-docs/releases/1.3.3/en/credential-data-model.html#mdoc-cbor-credential-format
287+ """
288+
288289 mdoci = MdocCborIssuer (
289290 private_key = self ._mso_mdoc_private_key ,
290291 alg = self ._mso_mdoc_private_key ["ALG" ],
291292 )
292- issuance_date = datetime .date .today ()
293+ if not (x5c_chain := self ._trust_evaluator .get_jwt_header_trust_parameters (issuer = self .entity_id ).get ("x5c" , [])):
294+ raise Exception ("missing x5c chain" )
295+
296+ for i in range (0 , len (x5c_chain )):
297+ x5c_chain [i ] = b"-----BEGIN CERTIFICATE-----\n " + x5c_chain [i ].encode ('utf-8' ) + b"\n -----END CERTIFICATE-----\n "
298+
299+ user_id , _ = user_entity
300+ credential : CredentialSpecificationConfig = cred_config .credential_specification [config .id ]
301+
302+ now = iat_now ()
303+ exp = exp_from_now (self .config_utils .get_jwt ().default_exp ) # TODO implement a config section for MobileSecurityObject and the related get_mso()
304+ nbf = now + cred_config .nbf_delta
305+ required_attrs = {"issuing_country" : cred_config .issuing_country , "issuing_authority" : cred_config .issuing_authority }
306+ supported_optional_attrs = {
307+ "sub" : str (uuid4 ()),
308+ "issuance_date" : datetime_from_timestamp (now ).strftime ('%Y-%m-%dT%H:%M:%SZ' ), #ISO 8601
309+ "expiry_date" : (datetime_from_timestamp (now ) + timedelta (credential .expiry_days )).strftime ('%Y-%m-%dT%H:%M:%SZ' ), # ISO 8601
310+ "trust_framework" : credential .trust_framework , "assurance_level" : credential .assurance_level
311+ }
312+
313+ holder_key = cbor2 .loads (CoseKey .from_pem_public_key (JWK (key = holder_key ).export_public_pem ()).encode ())
314+ status_list = None
315+ if credential .optional_mso_attrs :
316+ if credential .optional_mso_attrs .get ("status" ):
317+ status_list = self .revoke_on_credential_reissuance (user_id , auth_session , config .id )
318+
293319 mdoci .new (
320+ status = status_list , devicekeyinfo = holder_key , x509_chain = x5c_chain ,
294321 doctype = config .doctype ,
295322 data = self ._loader (
296323 user_entity ,
297324 credential .template ,
298325 CredentialConfigurationFormatEnum .MSO_MDOC .value ,
326+ extra_claims = required_attrs | supported_optional_attrs
299327 ),
300328 validity = {
301- "issuance_date" : issuance_date .isoformat (),
302- "expiry_date" : (
303- issuance_date + timedelta (credential .expiry_days )
304- ).isoformat (),
329+ "issuance_date" : datetime_from_timestamp (nbf ).strftime ("%Y-%m-%d" ),
330+ "expiry_date" : datetime_from_timestamp (exp ).strftime ("%Y-%m-%d" ) #pymdoccbor==1.3.0 needs iso format "%Y-%m-%d"
305331 },
306332 )
307- return mdoci .dumps ().decode ()
333+ issuer_signed_data = mdoci .signed ["documents" ][0 ]['issuerSigned' ]
334+ data = cbor2 .dumps (issuer_signed_data , canonical = True )
335+ return base64 .urlsafe_b64encode (data ).decode ()
308336
309337 def _issue_sd_jwt (
310- self , user_entity : tuple [str , UserEntity ], auth_session : AuthorizationSession ,
311- cred_config : CredentialConfigurationsConfig , iss_cred_supp_conf : CredentialConfiguration , holder_key : dict = None
312- ) -> dict :
338+ self , user_entity : tuple [str , UserEntity ], auth_session : AuthorizationSession , cred_config : CredentialConfigurationsConfig ,
339+ iss_cred_supp_conf : CredentialConfiguration , holder_key : dict | None = None ) -> str :
313340 """Reference: https://italia.github.io/eid-wallet-it-docs/releases/1.3.3/en/credential-data-model.html#digital-credential-sd-jwt-metadata-attributes"""
314341 now = iat_now ()
315- exp = exp_from_now (self .config_utils .get_jwt ().default_exp ) # TODO check date_of_expiry
342+ exp = exp_from_now (self .config_utils .get_jwt ().default_exp )
316343 cred_type_id = iss_cred_supp_conf .id
317344 cred_specification : CredentialSpecificationConfig = cred_config .credential_specification [cred_type_id ]
318345 required_claims = {"iss" : self .entity_id , "exp" : exp , "issuing_authority" : cred_config .issuing_authority , "issuing_country" : cred_config .issuing_country ,
@@ -340,11 +367,7 @@ def _issue_sd_jwt(
340367 issuer = self .entity_id
341368 ),
342369 )
343-
344- return {
345- "jws" : sdjwt_at_issuer .serialized_sd_jwt ,
346- "issuance" : sdjwt_at_issuer .sd_jwt_issuance ,
347- }
370+ return sdjwt_at_issuer .sd_jwt_issuance
348371
349372 @staticmethod
350373 def _retrieve_user_data (
@@ -366,13 +389,16 @@ def _loader(
366389 match credential_type :
367390 case CredentialConfigurationFormatEnum .SD_JWT .value :
368391 template = Template (template )
392+ # TODO remove and generalize it to simulate data recovery from an Authentic Source
369393 user_data = self ._retrieve_user_data (user_data )
370394 user_data = user_data | (extra_claims or {})
371395 json_filled = template .render (** user_data )
372396 return yaml_load_specification (json_filled )
373397 case CredentialConfigurationFormatEnum .MSO_MDOC .value :
398+ user_data = self ._retrieve_user_data (user_entity ) # TODO remove and generalize it to simulate data recovery from an Authentic Source
399+ data = user_data | (extra_claims or {})
374400 data = render_mso_mdoc_template (
375- template , user_data . model_dump () , FIELD_TRANSFORMS
401+ template , data , FIELD_TRANSFORMS
376402 )
377403 return data
378404 case _:
0 commit comments