diff --git a/data/issuer-config.json b/data/issuer-config.json index 33e6701..7ca88b4 100644 --- a/data/issuer-config.json +++ b/data/issuer-config.json @@ -1,1042 +1,1295 @@ { "credential_issuer": "https://server.example.com", - "authorization_server": "https://server.example.com", + "authorization_servers": ["https://server.example.com"], "credential_endpoint": "https://server.example.com/credential", "deferred_credential_endpoint": "https://server.example.com/credential_deferred", + "notification_endpoint": "https://server.example.com/notification", + "batch_credential_endpoint": "https://server.example.com/batch", "display": [ { - "name": "UAegean", - "location": "UAegean", + "name": "UAegean Test Issuer", + "location": "Greece", "locale": "en-GB", - "description": "For queries about how we are managing your data please contact the Data Protection Officer." + "description": "For queries about how we manage your data please contact the Data Protection Officer." } ], - "credentials_supported": { - "VerifiablePortableDocumentA1": { - "format": "vc+sd-jwt", - "scope": "VerifiablePortableDocumentA1", + "credential_configurations_supported": { + "VerifiablePortableDocumentA1SDJWT": { + "scope": "VerifiablePortableDocumentA1SDJWT", + "claims": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-GB" + }, + { + "name": "Vorname", + "locale": "de-DE" + } + ] + }, + "last_name": { + "display": [ + { + "name": "Surname", + "locale": "en-GB" + }, + { + "name": "Nachname", + "locale": "de-DE" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { - "name": "Portable Document A1", + "name": "SD-JWT Portable Document A1", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF" } ], - "credential_definition": { - "vct": "VerifiablePortableDocumentA1", - "claims": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-GB" - }, - { - "name": "Vorname", - "locale": "de-DE" - } - ] - }, - "last_name": { - "display": [ - { - "name": "Surname", - "locale": "en-GB" - }, - { - "name": "Nachname", - "locale": "de-DE" - } - ] - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + + "vct": "VerifiablePortableDocumentA1SDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } }, - "VerifiablePortableDocumentA2": { - "format": "jwt_vc_json", - "scope": "VerifiablePortableDocumentA2", + "VerifiablePortableDocumentA2SDJWT": { + "scope": "VerifiablePortableDocumentA2SDJWT", + "claims": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-GB" + }, + { + "name": "Vorname", + "locale": "de-DE" + } + ] + }, + "last_name": { + "display": [ + { + "name": "Surname", + "locale": "en-GB" + }, + { + "name": "Nachname", + "locale": "de-DE" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { - "name": "Portable Document A2", + "name": "SD-JWT Portable Document A2", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF" } ], - "credential_definition": { - "type": ["VerifiablePortableDocumentA2"], - "claims": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-GB" - }, - { - "name": "Vorname", - "locale": "de-DE" - } - ] - }, - "last_name": { - "display": [ - { - "name": "Surname", - "locale": "en-GB" - }, - { - "name": "Nachname", - "locale": "de-DE" - } - ] - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + + "vct": "VerifiablePortableDocumentA2SDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } }, - "PID": { - "format": "jwt_vc_json", + + "VerifiablePIDSDJWT": { "scope": "PID", + "claims": { + "given_name": { + "display": [ + { + "name": "Given Name", + "locale": "en-GB" + } + ] + }, + "family_name": { + "display": [ + { + "name": "Surname", + "locale": "en-GB" + } + ] + }, + "birth_date": { + "display": [ + { + "name": "Birth Date", + "locale": "en-GB" + } + ] + }, + "age_over_18": { + "display": [ + { + "name": "Age Over 18", + "locale": "en-GB" + } + ] + }, + "issuance_date": { + "display": [ + { + "name": "Issuance Date", + "locale": "en-GB" + } + ] + }, + "expiry_date": { + "display": [ + { + "name": "Expiry Date", + "locale": "en-GB" + } + ] + }, + "issuing_authority": { + "display": [ + { + "name": "Issuing Authority", + "locale": "en-GB" + } + ] + }, + "issuing_country": { + "display": [ + { + "name": "Issuing Country", + "locale": "en-GB" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { "name": "PID", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF", - "logo":{ - "url":"https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", - "alt_text":"UAegean" - + "logo": { + "uri": "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + "alt_text": "UAegean" } } ], - "credential_definition": { - "type": ["PID"], - "claims": { - "given_name": { - "display": [ - { - "name": "Given Name", - "locale": "en-GB" - } - ] - }, - "family_name": { - "display": [ - { - "name": "Surname", - "locale": "en-GB" - } - ] - }, - "birth_date": { - "display": [ - { - "name": "Birth Date", - "locale": "en-GB" - } - ] - }, - "age_over_18": { - "display": [ - { - "name": "Age Over 18", - "locale": "en-GB" - } - ] - }, - "issuance_date": { - "display": [ - { - "name": "Issuance Date", - "locale": "en-GB" - } - ] - }, - "expiry_date": { - "display": [ - { - "name": "Expiry Date", - "locale": "en-GB" - } - ] - }, - "issuing_authority": { - "display": [ - { - "name": "Issuing Authority", - "locale": "en-GB" - } - ] - }, - "issuing_country": { - "display": [ - { - "name": "Issuing Country", - "locale": "en-GB" - } - ] - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + "vct": "VerifiablePIDSDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } }, - "ePassportCredential": { - "format": "jwt_vc_json", - "scope": "ePassportCredential", - "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], - "display": [ - { - "name": "ePassportCredential", - "locale": "en-GB", - "background_color": "#12107c", - "text_color": "#FFFFFF", - "logo":{ - "url":"https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", - "alt_text":"UAegean" - - } - } - ], - "credential_definition": { - "type": ["ePassportCredential"], - "claims": { - "id": { - "display": [ - { - "name": "id", - "locale": "en-GB" - } - ] - }, - "electronicPassport": { - "display": [ - { - "name": "electronicPassport", - "locale": "en-GB" - } - ], - "properties": { - "dataGroup1": { - "display": [ - { - "name": "dataGroup1", - "locale": "en-GB" - } - ], - "properties": { - "birthdate": { - "display": [ - { - "name": "birthdate", - "locale": "en-GB" - } - ] - }, - "docTypeCode": { - "display": [ - { - "name": "docTypeCode", - "locale": "en-GB" - } - ] - }, - "expiryDate": { - "display": [ - { - "name": "expiryDate", - "locale": "en-GB" - } - ] - }, - "genderCode": { - "display": [ - { - "name": "genderCode", - "locale": "en-GB" - } - ] - }, - "holdersName": { - "display": [ - { - "name": "holdersName", - "locale": "en-GB" - } - ] - }, - "issuerCode": { - "display": [ - { - "name": "issuerCode", - "locale": "en-GB" - } - ] - }, - "natlText": { - "display": [ - { - "name": "natlText", - "locale": "en-GB" - } - ] - }, - "passportNumberIdentifier": { - "display": [ - { - "name": "passportNumberIdentifier", - "locale": "en-GB" - } - ] - } + "VerifiableePassportCredentialSDJWT": { + "scope": "ePassportCredential", + "claims": { + "id": { + "display": [ + { + "name": "id", + "locale": "en-GB" + } + ] + }, + "electronicPassport": { + "display": [ + { + "name": "electronicPassport", + "locale": "en-GB" + } + ], + "properties": { + "dataGroup1": { + "display": [ + { + "name": "dataGroup1", + "locale": "en-GB" } - }, - "dataGroup15": { - "display": [ - { - "name": "dataGroup15", - "locale": "en-GB" - } - ], - "properties": { - "activeAuthentication": { - "display": [ - { - "name": "activeAuthentication", - "locale": "en-GB" - } - ], - "properties": { - "publicKeyBinaryObject": { - "display": [ - { - "name": "publicKeyBinaryObject", - "locale": "en-GB" - } - ] - } + ], + "properties": { + "birthdate": { + "display": [ + { + "name": "birthdate", + "locale": "en-GB" } - } - } - }, - "dataGroup2EncodedFaceBiometrics": { - "display": [ - { - "name": "dataGroup2EncodedFaceBiometrics", - "locale": "en-GB" - } - ], - "properties": { - "faceBiometricDataEncodedPicture": { - "display": [ - { - "name": "faceBiometricDataEncodedPicture", - "locale": "en-GB" - } - ] - } - } - }, - "digitalTravelCredential": { - "display": [ - { - "name": "digitalTravelCredential", - "locale": "en-GB" - } - ], - "properties": { - "contentInfo": { - "display": [ - { - "name": "contentInfo", - "locale": "en-GB" - } - ], - "properties": { - "versionNumber": { - "display": [ - { - "name": "versionNumber", - "locale": "en-GB" - } - ] - }, - "signatureInfo": { - "display": [ - { - "name": "signatureInfo", - "locale": "en-GB" - } - ], - "properties": { - "digestHashAlgorithmIdentifier": { - "display": [ - { - "name": "digestHashAlgorithmIdentifier", - "locale": "en-GB" - } - ] - }, - "signatureAlgorithmIdentifier": { - "display": [ - { - "name": "signatureAlgorithmIdentifier", - "locale": "en-GB" - } - ] - }, - "signatureCertificateText": { - "display": [ - { - "name": "signatureCertificateText", - "locale": "en-GB" - } - ] - }, - "signatureDigestResultBinaryObject": { - "display": [ - { - "name": "signatureDigestResultBinaryObject", - "locale": "en-GB" - } - ] - }, - "signedAttributes": { - "display": [ - { - "name": "signedAttributes", - "locale": "en-GB" - } - ], - "properties": { - "attributeTypeCode": { - "display": [ - { - "name": "attributeTypeCode", - "locale": "en-GB" - } - ] - }, - "attributeValueText": { - "display": [ - { - "name": "attributeValueText", - "locale": "en-GB" - } - ] - } - } - } - } - } + ] + }, + "docTypeCode": { + "display": [ + { + "name": "docTypeCode", + "locale": "en-GB" } - }, - "dataCapabilitiesInfo": { - "display": [ - { - "name": "dataCapabilitiesInfo", - "locale": "en-GB" - } - ], - "properties": { - "dataTransferInterfaceTypeCode": { - "display": [ - { - "name": "dataTransferInterfaceTypeCode", - "locale": "en-GB" - } - ] - }, - "securityAssuranceLevelIndText": { - "display": [ - { - "name": "securityAssuranceLevelIndText", - "locale": "en-GB" - } - ] - }, - "userConsentInfoText": { - "display": [ - { - "name": "userConsentInfoText", - "locale": "en-GB" - } - ] - }, - "virtualComponentPresenceInd": { - "display": [ - { - "name": "virtualComponentPresenceInd", - "locale": "en-GB" - } - ] - } + ] + }, + "expiryDate": { + "display": [ + { + "name": "expiryDate", + "locale": "en-GB" } - }, - "dataContent": { - "display": [ - { - "name": "dataContent", - "locale": "en-GB" - } - ], - "properties": { - "dataGroup1": { - "display": [ - { - "name": "dataGroup1", - "locale": "en-GB" - } - ], - "properties": { - "birthdate": { - "display": [ - { - "name": "birthdate", - "locale": "en-GB" - } - ] - }, - "docTypeCode": { - "display": [ - { - "name": "docTypeCode", - "locale": "en-GB" - } - ] - }, - "expiryDate": { - "display": [ - { - "name": "expiryDate", - "locale": "en-GB" - } - ] - }, - "genderCode": { - "display": [ - { - "name": "genderCode", - "locale": "en-GB" - } - ] - }, - "holdersName": { - "display": [ - { - "name": "holdersName", - "locale": "en-GB" - } - ] - }, - "issuerCode": { - "display": [ - { - "name": "issuerCode", - "locale": "en-GB" - } - ] - }, - "natlText": { - "display": [ - { - "name": "natlText", - "locale": "en-GB" - } - ] - }, - "passportNumberIdentifier": { - "display": [ - { - "name": "passportNumberIdentifier", - "locale": "en-GB" - } - ] - }, - "personalNumberIdentifier": { - "display": [ - { - "name": "personalNumberIdentifier", - "locale": "en-GB" - } - ] - } - } - }, - "dataGroup2EncodedFaceBiometrics": { - "display": [ - { - "name": "dataGroup2EncodedFaceBiometrics", - "locale": "en-GB" - } - ], - "properties": { - "faceBiometricDataEncodedPicture": { - "display": [ - { - "name": "faceBiometricDataEncodedPicture", - "locale": "en-GB" - } - ] - } - } - }, - "docSecurityObject": { - "display": [ - { - "name": "docSecurityObject", - "locale": "en-GB" - } - ], - "properties": { - "dataGroupHash": { - "display": [ - { - "name": "dataGroupHash", - "locale": "en-GB" - } - ], - "properties": { - "dataGroupNumber": { - "display": [ - { - "name": "dataGroupNumber", - "locale": "en-GB" - } - ] - }, - "valueBinaryObject": { - "display": [ - { - "name": "valueBinaryObject", - "locale": "en-GB" - } - ] - } - } - }, - "digestHashAlgorithmIdentifier": { - "display": [ - { - "name": "digestHashAlgorithmIdentifier", - "locale": "en-GB" - } - ] - }, - "versionNumber": { - "display": [ - { - "name": "versionNumber", - "locale": "en-GB" - } - ] - } - } - } + ] + }, + "genderCode": { + "display": [ + { + "name": "genderCode", + "locale": "en-GB" + } + ] + }, + "holdersName": { + "display": [ + { + "name": "holdersName", + "locale": "en-GB" + } + ] + }, + "issuerCode": { + "display": [ + { + "name": "issuerCode", + "locale": "en-GB" + } + ] + }, + "natlText": { + "display": [ + { + "name": "natlText", + "locale": "en-GB" } - }, - "docSecurityObject": { - "display": [ - { - "name": "docSecurityObject", - "locale": "en-GB" - } - ], - "properties": { - "dataGroupHash": { - "display": [ - { - "name": "dataGroupHash", - "locale": "en-GB" - } - ], - "properties": { - "dataGroupNumber": { - "display": [ - { - "name": "dataGroupNumber", - "locale": "en-GB" - } - ] - }, - "valueBinaryObject": { - "display": [ - { - "name": "valueBinaryObject", - "locale": "en-GB" - } - ] - } + ] + }, + "passportNumberIdentifier": { + "display": [ + { + "name": "passportNumberIdentifier", + "locale": "en-GB" + } + ] + } + } + }, + "dataGroup15": { + "display": [ + { + "name": "dataGroup15", + "locale": "en-GB" + } + ], + "properties": { + "activeAuthentication": { + "display": [ + { + "name": "activeAuthentication", + "locale": "en-GB" + } + ], + "properties": { + "publicKeyBinaryObject": { + "display": [ + { + "name": "publicKeyBinaryObject", + "locale": "en-GB" } - }, - "digestHashAlgorithmIdentifier": { - "display": [ - { - "name": "digestHashAlgorithmIdentifier", - "locale": "en-GB" - } - ] - }, - "versionNumber": { - "display": [ - { - "name": "versionNumber", - "locale": "en-GB" - } - ] - } + ] } } } } + }, + "dataGroup2EncodedFaceBiometrics": { + "display": [ + { + "name": "dataGroup2EncodedFaceBiometrics", + "locale": "en-GB" + } + ], + "properties": { + "faceBiometricDataEncodedPicture": { + "display": [ + { + "name": "faceBiometricDataEncodedPicture", + "locale": "en-GB" + } + ] + } + } } } } + }, + "cryptographic_binding_methods_supported": ["jwk"], + "display": [ + { + "name": "ePassportCredential", + "locale": "en-GB", + "background_color": "#12107c", + "text_color": "#FFFFFF", + "logo": { + "uri": "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + "alt_text": "UAegean" + } + } + ], + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + "vct": "VerifiableePassportCredentialSDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] + } } }, - "StudentID": { - "format": "jwt_vc_json", + "VerifiableStudentIDSDJWT": { "scope": "StudentID", + "claims": { + "identifier": { + "display": [ + { + "name": "Identifier", + "locale": "en-GB" + } + ] + }, + "schacPersonalUniqueCode": { + "display": [ + { + "name": "SCHAC Personal Unique Code", + "locale": "en-GB" + } + ] + }, + "schacPersonalUniqueID": { + "display": [ + { + "name": "SCHAC Personal Unique ID", + "locale": "en-GB" + } + ] + }, + "schacHomeOrganization": { + "display": [ + { + "name": "SCHAC Home Organization", + "locale": "en-GB" + } + ] + }, + "familyName": { + "display": [ + { + "name": "Family Name", + "locale": "en-GB" + } + ] + }, + "firstName": { + "display": [ + { + "name": "First Name", + "locale": "en-GB" + } + ] + }, + "displayName": { + "display": [ + { + "name": "Display Name", + "locale": "en-GB" + } + ] + }, + "dateOfBirth": { + "display": [ + { + "name": "Date of Birth", + "locale": "en-GB" + } + ] + }, + "commonName": { + "display": [ + { + "name": "Common Name", + "locale": "en-GB" + } + ] + }, + "mail": { + "display": [ + { + "name": "Email", + "locale": "en-GB" + } + ] + }, + "eduPersonPrincipalName": { + "display": [ + { + "name": "eduPerson Principal Name", + "locale": "en-GB" + } + ] + }, + "eduPersonPrimaryAffiliation": { + "display": [ + { + "name": "eduPerson Primary Affiliation", + "locale": "en-GB" + } + ] + }, + "eduPersonAffiliation": { + "display": [ + { + "name": "eduPerson Affiliation", + "locale": "en-GB" + } + ] + }, + "eduPersonScopedAffiliation": { + "display": [ + { + "name": "eduPerson Scoped Affiliation", + "locale": "en-GB" + } + ] + }, + "eduPersonAssurance": { + "display": [ + { + "name": "eduPerson Assurance", + "locale": "en-GB" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { "name": "StudentID", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF", - "logo":{ - "url":"https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", - "alt_text":"UAegean" - + "logo": { + "uri": "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + "alt_text": "UAegean" } } ], - "credential_definition": { - "type": ["StudentID"], - "claims": { - "identifier": { - "display": [ - { - "name": "Identifier", - "locale": "en-GB" - } - ] - }, - "schacPersonalUniqueCode": { - "display": [ - { - "name": "SCHAC Personal Unique Code", - "locale": "en-GB" - } - ] - }, - "schacPersonalUniqueID": { - "display": [ - { - "name": "SCHAC Personal Unique ID", - "locale": "en-GB" - } - ] - }, - "schacHomeOrganization": { - "display": [ - { - "name": "SCHAC Home Organization", - "locale": "en-GB" - } - ] - }, - "familyName": { - "display": [ - { - "name": "Family Name", - "locale": "en-GB" - } - ] - }, - "firstName": { - "display": [ - { - "name": "First Name", - "locale": "en-GB" - } - ] - }, - "displayName": { - "display": [ - { - "name": "Display Name", - "locale": "en-GB" - } - ] - }, - "dateOfBirth": { - "display": [ - { - "name": "Date of Birth", - "locale": "en-GB" - } - ] - }, - "commonName": { - "display": [ - { - "name": "Common Name", - "locale": "en-GB" - } - ] - }, - "mail": { - "display": [ - { - "name": "Email", - "locale": "en-GB" - } - ] - }, - "eduPersonPrincipalName": { - "display": [ - { - "name": "eduPerson Principal Name", - "locale": "en-GB" - } - ] - }, - "eduPersonPrimaryAffiliation": { - "display": [ - { - "name": "eduPerson Primary Affiliation", - "locale": "en-GB" - } - ] - }, - "eduPersonAffiliation": { - "display": [ - { - "name": "eduPerson Affiliation", - "locale": "en-GB" - } - ] - }, - "eduPersonScopedAffiliation": { - "display": [ - { - "name": "eduPerson Scoped Affiliation", - "locale": "en-GB" - } - ] - }, - "eduPersonAssurance": { - "display": [ - { - "name": "eduPerson Assurance", - "locale": "en-GB" - } - ] - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + "vct": "VerifiableStudentIDSDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } }, - "allianceIDCredential": { - "format": "jwt_vc_json", - "scope": "allianceIDCredential", + + "VerifiableFerryBoardingPassCredentialSDJWT": { + "scope": "ferryBoardingPassCredential", + "claims": { + "identifier": { + "display": [ + { + "name": "Identifier", + "locale": "en-GB" + } + ] + }, + "ticketQR": { + "display": [ + { + "name": "Ticket QR Code", + "locale": "en-GB" + } + ] + }, + "ticketNumber": { + "display": [ + { + "name": "Ticket Number", + "locale": "en-GB" + } + ] + }, + "ticketLet": { + "display": [ + { + "name": "Ticket Letter", + "locale": "en-GB" + } + ] + }, + "lastName": { + "display": [ + { + "name": "Last Name", + "locale": "en-GB" + } + ] + }, + "firstName": { + "display": [ + { + "name": "First Name", + "locale": "en-GB" + } + ] + }, + "seatType": { + "display": [ + { + "name": "Seat Type", + "locale": "en-GB" + } + ] + }, + "seatNumber": { + "display": [ + { + "name": "Seat Number", + "locale": "en-GB" + } + ] + }, + "departureDate": { + "display": [ + { + "name": "Departure Date", + "locale": "en-GB" + } + ] + }, + "departureTime": { + "display": [ + { + "name": "Departure Time", + "locale": "en-GB" + } + ] + }, + "arrivalDate": { + "display": [ + { + "name": "Arrival Date", + "locale": "en-GB" + } + ] + }, + "arrivalTime": { + "display": [ + { + "name": "Arrival Time", + "locale": "en-GB" + } + ] + }, + "arrivalPort": { + "display": [ + { + "name": "Arrival Port", + "locale": "en-GB" + } + ] + }, + "vesselDescription": { + "display": [ + { + "name": "Vessel Description", + "locale": "en-GB" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { - "name": "AllianceIDCredential", + "name": "Ferry Boarding Pass", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF", - "logo":{ - "url":"https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", - "alt_text":"UAegean" - + "logo": { + "uri": "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + "alt_text": "UAegean" } } ], - "credential_definition": { - "type": ["allianceIDCredential"], - "claims": { - "identifier": { - "display": [ - { - "name": "Identifier", - "locale": "en-GB" - } - ], - "properties": { - "schemeID": { - "display": [ - { - "name": "Scheme ID", - "locale": "en-GB" - } - ] - }, - "value": { - "display": [ - { - "name": "Value", - "locale": "en-GB" - } - ] - }, - "id": { - "display": [ - { - "name": "ID", - "locale": "en-GB" - } - ] - } - } - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + "vct": "VerifiableFerryBoardingPassCredentialSDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } }, - "ferryBoardingPassCredential": { - "format": "jwt_vc_json", - "scope": "ferryBoardingPassCredential", + + "VerifiableReceipt": { + "scope": "vReceipt", + "claims": { + "MonetaryTotal.lineExtensionAmount": { + "display": [ + { + "name": "Line Extension Amount", + "locale": "en-GB" + } + ] + }, + "MonetaryTotal.taxInclusiveAmount": { + "display": [ + { + "name": "Tax Inclusive Amount", + "locale": "en-GB" + } + ] + }, + "MonetaryTotal.payableAmount": { + "display": [ + { + "name": "Payable Amount", + "locale": "en-GB" + } + ] + }, + "TaxTotal.taxSubtotal_": { + "display": [ + { + "name": "Tax Subtotal", + "locale": "en-GB" + } + ] + }, + "TaxTotal.taxAmount": { + "display": [ + { + "name": "Tax Amount", + "locale": "en-GB" + } + ] + }, + "Address.streetName": { + "display": [ + { + "name": "Street Name", + "locale": "en-GB" + } + ] + }, + "Address.cityName": { + "display": [ + { + "name": "City Name", + "locale": "en-GB" + } + ] + }, + "Address.postcode": { + "display": [ + { + "name": "Postcode", + "locale": "en-GB" + } + ] + }, + "Address.countryIdentifier": { + "display": [ + { + "name": "Country", + "locale": "en-GB" + } + ] + }, + "TaxCategory.taxScheme_": { + "display": [ + { + "name": "Tax Scheme", + "locale": "en-GB" + } + ] + }, + "ItemProperty.itemPropertyName": { + "display": [ + { + "name": "Item Property Name", + "locale": "en-GB" + } + ] + }, + "ItemProperty.value": { + "display": [ + { + "name": "Item Property Value", + "locale": "en-GB" + } + ] + }, + "TaxScheme.taxSchemeName": { + "display": [ + { + "name": "Tax Scheme Name", + "locale": "en-GB" + } + ] + }, + "AllowanceCharge.amount": { + "display": [ + { + "name": "Allowance Charge Amount", + "locale": "en-GB" + } + ] + }, + "AllowanceCharge.allowanceChargeReason": { + "display": [ + { + "name": "Allowance Charge Reason", + "locale": "en-GB" + } + ] + }, + "PartyName.name": { + "display": [ + { + "name": "Party Name", + "locale": "en-GB" + } + ] + }, + "PaymentMeans.cardAccount_": { + "display": [ + { + "name": "Card Account", + "locale": "en-GB" + } + ] + }, + "PaymentMeans.paymentMeansCode": { + "display": [ + { + "name": "Payment Means Code", + "locale": "en-GB" + } + ] + }, + "PartyIdentification.iD": { + "display": [ + { + "name": "Party Identification ID", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.paymentMeans_": { + "display": [ + { + "name": "Payment Means", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.note": { + "display": [ + { + "name": "Note", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.delivery_": { + "display": [ + { + "name": "Delivery", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.taxIncludedIndicator": { + "display": [ + { + "name": "Tax Included Indicator", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.taxTotal_": { + "display": [ + { + "name": "Tax Total", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.accountingCustomerParty_": { + "display": [ + { + "name": "Accounting Customer Party", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.documentCurrencyCode": { + "display": [ + { + "name": "Document Currency Code", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.payment_": { + "display": [ + { + "name": "Payment", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.sellerSupplierParty": { + "display": [ + { + "name": "Seller Supplier Party", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.legalMonetaryTotal": { + "display": [ + { + "name": "Legal Monetary Total", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.salesDocumentReference": { + "display": [ + { + "name": "Sales Document Reference", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.iD": { + "display": [ + { + "name": "Purchase Receipt ID", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.issueDate": { + "display": [ + { + "name": "Issue Date", + "locale": "en-GB" + } + ] + }, + "PurchaseReceipt.purchaseReceiptLine-1": { + "display": [ + { + "name": "Purchase Receipt Line", + "locale": "en-GB" + } + ] + }, + "TaxSubtotal.taxableAmount": { + "display": [ + { + "name": "Taxable Amount", + "locale": "en-GB" + } + ] + }, + "TaxSubtotal.taxSubtotalTaxAmount": { + "display": [ + { + "name": "Tax Subtotal Amount", + "locale": "en-GB" + } + ] + }, + "TaxSubtotal.taxCategory_": { + "display": [ + { + "name": "Tax Category", + "locale": "en-GB" + } + ] + }, + "TaxSubtotal.percent": { + "display": [ + { + "name": "Tax Percent", + "locale": "en-GB" + } + ] + }, + "Item.commodityClassification_": { + "display": [ + { + "name": "Commodity Classification", + "locale": "en-GB" + } + ] + }, + "Item.itemInstance_": { + "display": [ + { + "name": "Item Instance", + "locale": "en-GB" + } + ] + }, + "Item.additionalItemProperty": { + "display": [ + { + "name": "Additional Item Property", + "locale": "en-GB" + } + ] + }, + "Payment.authorizationID": { + "display": [ + { + "name": "Authorization ID", + "locale": "en-GB" + } + ] + }, + "Payment.paidAmount": { + "display": [ + { + "name": "Paid Amount", + "locale": "en-GB" + } + ] + }, + "Payment.transactionID": { + "display": [ + { + "name": "Transaction ID", + "locale": "en-GB" + } + ] + }, + "SupplierParty.party_": { + "display": [ + { + "name": "Supplier Party", + "locale": "en-GB" + } + ] + }, + "SupplierParty.supplierPartyID": { + "display": [ + { + "name": "Supplier Party ID", + "locale": "en-GB" + } + ] + }, + "Party.partyIdentification_.iD": { + "display": [ + { + "name": "Party Identification ID", + "locale": "en-GB" + } + ] + }, + "Party.partyName_.name": { + "display": [ + { + "name": "Party Name", + "locale": "en-GB" + } + ] + }, + "Party.postalAddress_.streetName": { + "display": [ + { + "name": "Street Name", + "locale": "en-GB" + } + ] + }, + "Party.postalAddress_.cityName": { + "display": [ + { + "name": "City Name", + "locale": "en-GB" + } + ] + }, + "Party.postalAddress_.postcode": { + "display": [ + { + "name": "Postcode", + "locale": "en-GB" + } + ] + }, + "Party.postalAddress_.countryIdentifier": { + "display": [ + { + "name": "Country", + "locale": "en-GB" + } + ] + }, + "CommodityClassification.itemClassificationCode": { + "display": [ + { + "name": "Item Classification Code", + "locale": "en-GB" + } + ] + }, + "CardAccount.networkID": { + "display": [ + { + "name": "Network ID", + "locale": "en-GB" + } + ] + }, + "CardAccount.accountNumberID": { + "display": [ + { + "name": "Account Number ID", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.partyIdentification_.iD": { + "display": [ + { + "name": "Customer Party Identification ID", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.partyName_.name": { + "display": [ + { + "name": "Customer Party Name", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.postalAddress_.streetName": { + "display": [ + { + "name": "Customer Party Street Name", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.postalAddress_.cityName": { + "display": [ + { + "name": "Customer Party City Name", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.postalAddress_.postcode": { + "display": [ + { + "name": "Customer Party Postcode", + "locale": "en-GB" + } + ] + }, + "CustomerParty.party_.postalAddress_.countryIdentifier": { + "display": [ + { + "name": "Customer Party Country", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.item_.commodityClassification_.itemClassificationCode": { + "display": [ + { + "name": "Purchase Receipt Line Item Classification Code", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.itemPropertyName": { + "display": [ + { + "name": "Purchase Receipt Line Item Property Name", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.value": { + "display": [ + { + "name": "Purchase Receipt Line Item Property Value", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.quantity": { + "display": [ + { + "name": "Purchase Receipt Line Quantity", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.allowanceCharge_.amount": { + "display": [ + { + "name": "Purchase Receipt Line Allowance Charge Amount", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.allowanceCharge_.allowanceChargeReason": { + "display": [ + { + "name": "Purchase Receipt Line Allowance Charge Reason", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.taxInclusiveLineExtentionAmount": { + "display": [ + { + "name": "Tax Inclusive Line Extension Amount", + "locale": "en-GB" + } + ] + }, + "PurchaseReceiptLine.iD": { + "display": [ + { + "name": "Purchase Receipt Line ID", + "locale": "en-GB" + } + ] + }, + "Delivery.actualDeliveryDate": { + "display": [ + { + "name": "Actual Delivery Date", + "locale": "en-GB" + } + ] + }, + "Delivery.deliveryAddress.streetName": { + "display": [ + { + "name": "Delivery Street Name", + "locale": "en-GB" + } + ] + }, + "Delivery.deliveryAddress.cityName": { + "display": [ + { + "name": "Delivery City Name", + "locale": "en-GB" + } + ] + }, + "Delivery.deliveryAddress.postcode": { + "display": [ + { + "name": "Delivery Postcode", + "locale": "en-GB" + } + ] + }, + "Delivery.deliveryAddress.countryIdentifier": { + "display": [ + { + "name": "Delivery Country", + "locale": "en-GB" + } + ] + }, + "Delivery.actualDeliveryTime": { + "display": [ + { + "name": "Actual Delivery Time", + "locale": "en-GB" + } + ] + }, + "DocumentReference.iD": { + "display": [ + { + "name": "Document Reference ID", + "locale": "en-GB" + } + ] + }, + "ItemInstance.additionalItemProperty.itemPropertyName": { + "display": [ + { + "name": "Item Instance Property Name", + "locale": "en-GB" + } + ] + }, + "ItemInstance.additionalItemProperty.value": { + "display": [ + { + "name": "Item Instance Property Value", + "locale": "en-GB" + } + ] + } + }, "cryptographic_binding_methods_supported": ["jwk"], - "cryptographic_suites_supported": ["ES256"], "display": [ { - "name": "Ferry Boarding Pass", + "name": "vReceipt", "locale": "en-GB", "background_color": "#12107c", "text_color": "#FFFFFF", - "logo":{ - "url":"https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", - "alt_text":"UAegean" - + "logo": { + "uri": "https://yourdomain.com/logo.png", + "alt_text": "vReceipt Logo" } } ], - "credential_definition": { - "type": ["ferryBoardingPassCredential"], - "claims": { - "identifier": { - "display": [ - { - "name": "Identifier", - "locale": "en-GB" - } - ] - }, - "ticketQR": { - "display": [ - { - "name": "Ticket QR Code", - "locale": "en-GB" - } - ] - }, - "ticketNumber": { - "display": [ - { - "name": "Ticket Number", - "locale": "en-GB" - } - ] - }, - "ticketLet": { - "display": [ - { - "name": "Ticket Letter", - "locale": "en-GB" - } - ] - }, - "lastName": { - "display": [ - { - "name": "Last Name", - "locale": "en-GB" - } - ] - }, - "firstName": { - "display": [ - { - "name": "First Name", - "locale": "en-GB" - } - ] - }, - "seatType": { - "display": [ - { - "name": "Seat Type", - "locale": "en-GB" - } - ] - }, - "seatNumber": { - "display": [ - { - "name": "Seat Number", - "locale": "en-GB" - } - ] - }, - "departureDate": { - "display": [ - { - "name": "Departure Date", - "locale": "en-GB" - } - ] - }, - "departureTime": { - "display": [ - { - "name": "Departure Time", - "locale": "en-GB" - } - ] - }, - "arrivalDate": { - "display": [ - { - "name": "Arrival Date", - "locale": "en-GB" - } - ] - }, - "arrivalTime": { - "display": [ - { - "name": "Arrival Time", - "locale": "en-GB" - } - ] - }, - "arrivalPort": { - "display": [ - { - "name": "Arrival Port", - "locale": "en-GB" - } - ] - }, - "vesselDescription": { - "display": [ - { - "name": "Vessel Description", - "locale": "en-GB" - } - ] - } + "credential_signing_alg_values_supported": ["ES256"], + "format": "vc+sd-jwt", + "vct": "VerifiablevReceiptSDJWT", + "proof_types_supported": { + "jwt": { + "proof_signing_alg_values_supported": ["ES256"] } } } diff --git a/data/oauth-config.json b/data/oauth-config.json index 3c47120..eaa1e92 100644 --- a/data/oauth-config.json +++ b/data/oauth-config.json @@ -1,63 +1,43 @@ { "issuer": "https://server.example.com", "authorization_endpoint": "https://server.example.com/authorize", + "pushed_authorization_request_endpoint": "https://server.example.com/par", + "require_pushed_authorization_requests": true, "token_endpoint": "https://server.example.com/token", "jwks_uri": "https://server.example.com/jwks", - "scopes_supported": [ - "openid" - ], - "response_types_supported": [ - "vp_token", - "id_token" - ], - "response_modes_supported": [ - "query" - ], - "grant_types_supported": [ - "authorization_code" - ], - "subject_types_supported": [ - "public" - ], - "id_token_signing_alg_values_supported": [ - "ES256" - ], - "request_object_signing_alg_values_supported": [ - "ES256" - ], + "scopes_supported": ["openid"], + "response_types_supported": ["code", "vp_token", "id_token"], + "response_modes_supported": ["query"], + "grant_types_supported": ["authorization_code","urn:ietf:params:oauth:grant-type:pre-authorized_code"], + "subject_types_supported": ["public", "pairwise"], + "id_token_signing_alg_values_supported": ["ES256"], + + "pre-authorized_grant_anonymous_access_supported": true, + + "request_object_signing_alg_values_supported": ["ES256"], "request_parameter_supported": true, "request_uri_parameter_supported": true, - "token_endpoint_auth_methods_supported": [ - "private_key_jwt" - ], + "token_endpoint_auth_methods_supported": ["none"], "request_authentication_methods_supported": { - "authorization_endpoint": [ - "request_object" - ] + "authorization_endpoint": ["request_object"] }, "vp_formats_supported": { "jwt_vp": { - "alg_values_supported": [ - "ES256" - ] + "alg_values_supported": ["ES256"] }, "jwt_vc_json": { - "alg_values_supported": [ - "ES256" - ] + "alg_values_supported": ["ES256"] } }, "subject_syntax_types_supported": [ "did:key:jwk_jcs-pub", "did:ebsi:v1", - "did:ebsi:v2" - ], - "subject_trust_frameworks_supported": [ - "ebsi", - "ewc-issuer-trust-list" + "did:ebsi:v2", + "did:jwk" ], + "subject_trust_frameworks_supported": ["ebsi", "ewc-issuer-trust-list"], "id_token_types_supported": [ "subject_signed_id_token", "attester_signed_id_token" ] -} \ No newline at end of file +} diff --git a/data/presentation_definition.json b/data/presentation_definition.json index dd1655f..fc7bad5 100644 --- a/data/presentation_definition.json +++ b/data/presentation_definition.json @@ -26,7 +26,7 @@ "path": ["$.vct"], "filter": { "type": "string", - "const": "VerifiablePortableDocumentA1" + "const": "VerifiablePortableDocumentA2" } } ], diff --git a/data/presentation_definition_sdjwt.json b/data/presentation_definition_sdjwt.json new file mode 100644 index 0000000..d17c69e --- /dev/null +++ b/data/presentation_definition_sdjwt.json @@ -0,0 +1,37 @@ +{ + "id": "d49ee616-0e8d-4698-aff5-2a8a2362652d", + "name": "id-card-proof", + "format": { + "vc+sd-jwt": { + "alg": ["ES256", "ES384"] + } + }, + "input_descriptors": [ + { + "id": "abd4acb1-1dcb-41ad-8596-ceb1401a69c7", + "format": { + "vc+sd-jwt": { + "alg": ["ES256", "ES384"] + } + }, + "constraints": { + "fields": [ + { + "path": ["$.given_name"] + }, + { + "path": ["$.last_name"] + }, + { + "path": ["$.vct"], + "filter": { + "type": "string", + "const": "VerifiablePortableDocumentA1SDJWT" + } + } + ], + "limit_disclosure": "required" + } + } + ] +} diff --git a/data/verifier-config copy.json b/data/verifier-config copy.json new file mode 100644 index 0000000..a8c689d --- /dev/null +++ b/data/verifier-config copy.json @@ -0,0 +1,35 @@ +{ + "client_id": "https://dss.aegean.gr", + "client_name": "UAegean EWC Verifier", + "vp_formats_supported": [ + "jwt_vc_json", + "ldp_vc" + ], + "vp_token_endpoint_auth_methods_supported": [ + "client_secret_basic", + "client_secret_post" + ], + "scopes_supported": [ + "openid", + "profile", + "vc_present" + ], + "response_types_supported": [ + "vp_token" + ], + "grant_types_supported": [ + "authorization_code" + ], + "id_token_signing_alg_values_supported": [ + "RS256", + "ES256" + ], + "vp_token_signing_alg_values_supported": [ + "RS256", + "ES256" + ], + "subject_types_supported": [ + "pairwise" + ] + } + \ No newline at end of file diff --git a/data/verifier-config.json b/data/verifier-config.json index a4f57df..3de0382 100644 --- a/data/verifier-config.json +++ b/data/verifier-config.json @@ -1,39 +1,7 @@ { - "client_id": "https://dss.aegean.gr", - "client_name": "UAegean EWC Verifier", - "redirect_uris": [ - "https://example-verifier.com/callback" - ], - "vp_formats_supported": [ - "jwt_vc_json", - "ldp_vc" - ], - "vp_token_endpoint_auth_methods_supported": [ - "client_secret_basic", - "client_secret_post" - ], - "presentation_definition_uri": "https://example-verifier.com/presentation-definitions", - "scopes_supported": [ - "openid", - "profile", - "vc_present" - ], - "response_types_supported": [ - "vp_token" - ], - "grant_types_supported": [ - "authorization_code" - ], - "id_token_signing_alg_values_supported": [ - "RS256", - "ES256" - ], - "vp_token_signing_alg_values_supported": [ - "RS256", - "ES256" - ], - "subject_types_supported": [ - "pairwise" - ] - } - \ No newline at end of file + "client_name": "UAegean EWC Verifier", + "logo_uri": "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + "location": "Greece", + "cover_uri": "string", + "description": "EWC pilot case verification" +} diff --git a/didjwks/did_private.key b/didjwks/did_private.key new file mode 100644 index 0000000..e5b0532 --- /dev/null +++ b/didjwks/did_private.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDfw3MPQvb1xZQSca9QrZvJXTpGquu+bmVT1+/eeGh8boAoGCCqGSM49 +AwEHoUQDQgAEijVgOGHvwHSeV1Z2iLF9pQLQAw7KcHF3VIjThhvVtBRJ8VKFYBQY +Sc3HbhXZvkbWOuEk0eYzC2BE4E6L04oktw== +-----END EC PRIVATE KEY----- diff --git a/didjwks/did_private_pkcs8.key b/didjwks/did_private_pkcs8.key new file mode 100644 index 0000000..9195105 --- /dev/null +++ b/didjwks/did_private_pkcs8.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN/Dcw9C9vXFlBJxr +1Ctm8ldOkaq675uZVPX7954aHxuhRANCAASKNWA4Ye/AdJ5XVnaIsX2lAtADDspw +cXdUiNOGG9W0FEnxUoVgFBhJzcduFdm+RtY64STR5jMLYETgTovTiiS3 +-----END PRIVATE KEY----- diff --git a/didjwks/did_public.pem b/didjwks/did_public.pem new file mode 100644 index 0000000..c004486 --- /dev/null +++ b/didjwks/did_public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEijVgOGHvwHSeV1Z2iLF9pQLQAw7K +cHF3VIjThhvVtBRJ8VKFYBQYSc3HbhXZvkbWOuEk0eYzC2BE4E6L04oktw== +-----END PUBLIC KEY----- diff --git a/openapi/conformance-backend-endpoint.yaml b/openapi/conformance-backend-endpoint.yaml new file mode 100644 index 0000000..fa994cd --- /dev/null +++ b/openapi/conformance-backend-endpoint.yaml @@ -0,0 +1,148 @@ +openapi: 3.0.0 +info: + title: ITB Conformance Backend + version: "1.0.0" +servers: + - description: Example conformance server + url: https://example.conformance.com +paths: + /offer-code-sd-jwt: + get: + summary: Credential offer (SD-JWT) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /offer-no-code: + get: + summary: Credential Offer without transaction code + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /offer-tx-code: + get: + summary: Credential Offer with transaction code + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /pre-offer-jwt-bpass: + get: + summary: Pre Authorised Credential Offer (Boarding Pass) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /pre-offer-jwt-edu: + get: + summary: Pre Authorised Credential Offer (Education ID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /pre-offer-jwt-alliance: + get: + summary: Pre Authorised Credential Offer (Alliance) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /pre-offer-jwt-passport: + get: + summary: Pre Authorised Credential Offer (Passport) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /pre-offer-jwt-pid: + get: + summary: Pre Authorised Credential Offer (PID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /offer-pid-persona: + get: + summary: Pre Authorised Credential Offer (PID with persona) + parameters: + - in: query + name: personaId + schema: + type: string + required: true + example: "2" + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /offer: + get: + summary: Credential Offer + responses: + "200": + description: Successful response + tags: + - "EWC RFC001: Issue Verifiable Credential" + /vp-request/pid: + get: + summary: Verification Request (PID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" + /vp-request/epassport: + get: + summary: Verification Request (EPassport) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" + /vp-request/educationid: + get: + summary: Verification Request (Education ID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" + /vp-request/allianceid: + get: + summary: Verification Request (Alliance ID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" + /vp-request/erua-id: + get: + summary: Verification Request (EURA ID) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" + /vp-request/ferryboardingpass: + get: + summary: Verification Request (Ferry Boarding Pass) + responses: + "200": + description: Successful response + tags: + - "EWC RFC002: Present Verifiable Credential" +tags: + - description: "This consists of conformance endpoints for EWC RFC001: Issue Verifiable Credential" + name: "EWC RFC001: Issue Verifiable Credential" + - description: "This consists of conformance endpoints for EWC RFC002: Present Verifiable Credential" + name: "EWC RFC002: Present Verifiable Credential" diff --git a/openapi/wallet-to-wallet-endpoints.yaml b/openapi/wallet-to-wallet-endpoints.yaml new file mode 100644 index 0000000..ce3e448 --- /dev/null +++ b/openapi/wallet-to-wallet-endpoints.yaml @@ -0,0 +1,1894 @@ +components: + schemas: + AuthorisationCodeGrant: + properties: + authorization_code: + description: Authorisation code grant + properties: + authorization_server: + description: '"OPTIONAL. String that the Wallet can use to identify + the Authorization Server to use with this grant type when `authorization_servers` + parameter in the Credential Issuer metadata has multiple entries. It + MUST NOT be used otherwise. The value of this parameter MUST match + with one of the values in the `authorization_servers` array obtained + from the Credential Issuer metadata." + + ' + type: string + issuer_state: + description: '"OPTIONAL. String value created by the Credential Issuer + and opaque to the Wallet that is used to bind the subsequent Authorization + Request with the Credential Issuer to a context set up during previous + steps. If the Wallet decides to use the Authorization Code Flow and + received a value for this parameter, it MUST include it in the subsequent + Authorization Request to the Credential Issuer as the `issuer_state` + parameter value" + + ' + type: string + type: object + required: + - authorization_code + title: Authorisation code grant + type: object + AuthorisationCodeTokenRequest: + $ref: '#/components/schemas/AuthorisationCodeGrant' + AuthorisationDetails: + properties: + credential_definition: + properties: + type: + items: + type: string + type: array + required: + - type + type: object + format: + type: string + locations: + items: + type: string + type: array + type: + type: string + required: + - type + - locations + - format + - credential_definition + title: Authorisation Details + type: object + AuthorisationResponse: + properties: + client_id: + description: Decentralised identifier + type: string + nonce: + description: A value used to associate a client session with an ID token + and to mitigate replay attacks. + type: string + redirect_uri: + description: For redirection of the response + type: string + request_uri: + description: The authorisation server's private key signed the request. + type: string + response_mode: + description: The value must be 'direct_post' + type: string + response_type: + description: The value must be 'id_token' if the issuer requests DID authentication. + type: string + scope: + description: The value must be 'openid' + type: string + state: + description: The client uses an opaque value to maintain the state between + the request and callback. + type: string + title: Authorisation response + type: object + AuthorisationServerMetadata: + description: As described in Chapter 3 of [OpenID Connect Discovery 1.0 specification](https://openid.net/specs/openid-connect-discovery-1_0.html). + properties: + authorization_endpoint: + description: REQUIRED. URL of the OP's OAuth 2.0 Authorization Endpoint + [OpenID.Core]. This URL MUST use the https scheme and MAY contain port, + path, and query parameter components. + type: string + id_token_signing_alg_values_supported: + description: REQUIRED. JSON array containing a list of the JWS signing algorithms + (alg values) supported by the OP for the ID Token to encode the Claims + in a JWT [JWT]. The algorithm RS256 MUST be included. The value none MAY + be supported but MUST NOT be used unless the Response Type used returns + no ID Token from the Authorization Endpoint (such as when using the Authorization + Code Flow). + items: + type: string + type: array + issuer: + description: REQUIRED. URL using the https scheme with no query or fragment + components that the OP asserts as its Issuer Identifier. + type: string + jwks_uri: + description: REQUIRED. URL of the OP's JWK Set [JWK] document, which MUST + use the https scheme. This contains the signing key(s) the RP uses to + validate signatures from the OP. The JWK Set MAY also contain the Server's + encryption key(s), which are used by RPs to encrypt requests to the Server. + When both signing and encryption keys are made available, a use (public + key use) parameter value is REQUIRED for all keys in the referenced JWK + Set to indicate each key's intended usage. Although some algorithms allow + the same key to be used for both signatures and encryption, doing so is + NOT RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used + to provide X.509 representations of keys provided. When used, the bare + key values MUST still be present and MUST match those in the certificate. + The JWK Set MUST NOT contain private or symmetric key values. + type: string + pre-authorized_grant_anonymous_access_supported: + description: OPTIONAL. A boolean indicating whether the Credential Issuer + accepts a Token Request with a Pre-Authorized Code but without a client_id. + The default is false. + type: boolean + pushed_authorization_request_endpoint: + description: The URL of the pushed authorization request endpoint at which + a client can post an authorization request to exchange for a request_uri + value usable at the authorization server. + type: string + require_pushed_authorization_requests: + description: Boolean parameter indicating whether the authorization server + accepts authorization request data only via PAR. If omitted, the default + value is false. + type: boolean + response_types_supported: + description: REQUIRED. JSON array containing a list of the OAuth 2.0 response_type + values that this OP supports. + items: + type: string + type: array + subject_types_supported: + description: REQUIRED. JSON array containing a list of the Subject Identifier + types that this OP supports. Valid types include pairwise and public. + items: + type: string + type: array + token_endpoint: + description: URL of the OP's OAuth 2.0 Token Endpoint [OpenID.Core]. This + URL MUST use the https scheme and MAY contain port, path, and query parameter + components. + type: string + required: + - issuer + - authorization_endpoint + - token_endpoint + - jwks_uri + - response_types_supported + - subject_types_supported + - id_token_signing_alg_values_supported + title: Authorisation Server Metadata + type: object + CredentialIssuerMetadata: + properties: + authorization_servers: + description: 'OPTIONAL. Array of strings, where each string is an identifier + of the OAuth 2.0 Authorization Server (as defined in [RFC8414]) the Credential + Issuer relies on for authorization. If this parameter is omitted, the + entity providing the Credential Issuer is also acting as the Authorization + Server, i.e., the Credential Issuer''s identifier is used to obtain the + Authorization Server metadata. The actual OAuth 2.0 Authorization Server + metadata is obtained from the oauth-authorization-server well-known location + as defined in Section 3 of [RFC8414]. When there are multiple entries + in the array, the Wallet may be able to determine which Authorization + Server to use by querying the metadata; for example, by examining the + grant_types_supported values, the Wallet can filter the server to use + based on the grant type it plans to use. When the Wallet is using authorization_server + parameter in the Credential Offer as a hint to determine which Authorization + Server to use out of multiple, the Wallet MUST NOT proceed with the flow + if the authorization_server Credential Offer parameter value does not + match any of the entries in the authorization_servers array. + + ' + type: string + batch_credential_endpoint: + description: OPTIONAL. URL of the Credential Issuer's Batch Credential Endpoint. + This URL MUST use the https scheme and MAY contain port, path, and query + parameter components. If omitted, the Credential Issuer does not support + the Batch Credential Endpoint. + type: string + credential_configurations_supported: + oneOf: + - $ref: '#/components/schemas/JwtVcJsonCredentialConfiguration' + - $ref: '#/components/schemas/IetfSdJwtVcCredentialConfiguration' + - $ref: '#/components/schemas/MsoMdocCredentialConfiguration' + credential_endpoint: + description: REQUIRED. URL of the Credential Issuer's Credential Endpoint. + This URL MUST use the https scheme and MAY contain port, path, and query + parameter components. + type: string + credential_identifiers_supported: + description: 'OPTIONAL. Boolean value specifying whether the Credential + Issuer supports returning credential_identifiers parameter in the authorization_details + Token Response parameter, with true indicating support. If omitted, the + default value is false. + + ' + type: boolean + credential_issuer: + description: REQUIRED. The Credential Issuer's identifier, as defined in + Section 11.2.1. + type: string + credential_response_encryption: + description: OPTIONAL. Object containing information about whether the Credential + Issuer supports encryption of the Credential and Batch Credential Response + on top of TLS. + properties: + alg_values_supported: + description: 'REQUIRED. Array containing a list of the JWE [RFC7516] + encryption algorithms (alg values) [RFC7518] supported by the Credential + and Batch Credential Endpoint to encode the Credential or Batch Credential + Response in a JWT [RFC7519]. + + ' + items: + type: string + type: array + enc_values_supported: + description: 'REQUIRED. Array containing a list of the JWE [RFC7516] + encryption algorithms (enc values) [RFC7518] supported by the Credential + and Batch Credential Endpoint to encode the Credential or Batch Credential + Response in a JWT [RFC7519]. + + ' + items: + type: string + type: array + encryption_required: + description: 'REQUIRED. Boolean value specifying whether the Credential + Issuer requires the additional encryption on top of TLS for the Credential + Response. If the value is true, the Credential Issuer requires encryption + for every Credential Response and therefore the Wallet MUST provide + encryption keys in the Credential Request. If the value is false, + the Wallet MAY chose whether it provides encryption keys or not. + + ' + type: boolean + type: object + deferred_credential_endpoint: + description: OPTIONAL. URL of the Credential Issuer's Deferred Credential + Endpoint. This URL MUST use the https scheme and MAY contain port, path, + and query parameter components. If omitted, the Credential Issuer does + not support the Deferred Credential Endpoint. + type: string + display: + description: OPTIONAL. Array of objects, where each object contains display + properties of a Credential Issuer for a certain language. + items: + properties: + locale: + description: String value that identifies the language of this object + represented as a language tag taken from values defined in BCP47 + [RFC5646]. There MUST be only one object for each language identifier. + type: string + logo: + description: OPTIONAL. Object with information about the logo of the + Credential Issuer. + properties: + alt_text: + description: 'OPTIONAL. String value of the alternative text for + the logo image. + + ' + type: string + uri: + description: 'REQUIRED. String value that contains a URI where + the Wallet can obtain the logo of the Credential Issuer. The + Wallet needs to determine the scheme, since the URI value could + use the https: scheme, the data: scheme, etc. + + ' + type: string + required: + - uri + - alt_text + type: object + name: + description: OPTIONAL. String value of a display name for the Credential + Issuer. + type: string + type: object + type: array + notification_endpoint: + description: OPTIONAL. URL of the Credential Issuer's Notification Endpoint. + This URL MUST use the https scheme and MAY contain port, path, and query + parameter components. If omitted, the Credential Issuer does not support + the Notification Endpoint. + type: string + signed_metadata: + description: 'OPTIONAL. String that is a signed JWT. This JWT contains + Credential Issuer metadata parameters as claims. The signed metadata + MUST be secured using JSON Web Signature (JWS) [RFC7515] and MUST contain + an iat (Issued At) claim, an iss (Issuer) claim denoting the party attesting + to the claims in the signed metadata, and sub (Subject) claim matching + the Credential Issuer identifier. If the Wallet supports signed metadata, + metadata values conveyed in the signed JWT MUST take precedence over the + corresponding values conveyed using plain JSON elements. If the Credential + Issuer wants to enforce use of signed metadata, it omits the respective + metadata parameters from the unsigned part of the Credential Issuer metadata. A + signed_metadata metadata value MUST NOT appear as a claim in the JWT. The + Wallet MUST establish trust in the signer of the metadata, and obtain + the keys to validate the signature before processing the metadata. The + concrete mechanism how to do that is out of scope of this specification + and MAY be defined in the profiles of this specification. + + ' + type: string + required: + - credential_issuer + - authorization_servers + - credential_endpoint + - credential_configurations_supported + title: Credential Issuer Metadata + type: object + CredentialOffer: + properties: + credential_configuration_ids: + description: '"REQUIRED. Array of unique strings that each identify one + of the keys in the name/value pairs stored in the `credential_configurations_supported` + Credential Issuer metadata. The Wallet uses these string values to obtain + the respective object that contains information about the Credential being + offered. For example, these string values can be used to obtain scope + values to be used in the Authorization Request." + + ' + items: + type: string + type: array + credential_issuer: + description: '"REQUIRED. The URL of the Credential Issuer, from which the + Wallet is requested to obtain one or more Credentials. The Wallet can + also use it to obtain the Credential Issuer''s Metadata." + + ' + type: string + grants: + oneOf: + - $ref: '#/components/schemas/AuthorisationCodeGrant' + - $ref: '#/components/schemas/PreAuthorisedCodeGrant' + required: + - credential_issuer + - credential_configuration_ids + - grants + title: Credential Offer + type: object + CredentialRequest: + properties: + format: + description: 'REQUIRED when the credential_identifiers parameter was not + returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + proof: + description: 'OPTIONAL. Contains the proof of possession of the cryptographic + key material the issued Credential would be bound to. + + ' + properties: + credential_identifier: + description: 'REQUIRED when credential_identifiers parameter was returned + from the Token Response. MUST NOT be used otherwise. + + ' + type: string + credential_response_encryption: + description: 'OPTIONAL. Contains information for encrypting the Credential + Response. + + ' + properties: + alg: + description: 'REQUIRED. JWE alg algorithm for encrypting Credential + Responses. + + ' + type: string + enc: + description: 'REQUIRED. JWE enc algorithm for encrypting Credential + Responses. + + ' + type: string + jwk: + description: 'REQUIRED. Contains a single public key as a JWK used + for encrypting the Credential Response. + + ' + type: object + type: object + proof_type: + description: 'REQUIRED. Denotes the key proof type. + + ' + type: string + type: object + required: + - proof_type + - jwk + - alg + - enc + type: object + DeferredCredentialErrorResponse: + properties: + error: + description: 'REQUIRED. Error code indicating the type of error that occurred. + + ' + enum: + - issuance_pending + - invalid_transaction_id + type: string + error_description: + description: 'OPTIONAL. Human-readable ASCII text providing additional information about + the error. + + ' + type: string + invalid_transaction_id: + description: 'Error response when the Deferred Credential Request contains + an invalid transaction_id. + + ' + type: string + issuance_pending: + description: 'Error response when the Credential issuance is still pending. + + ' + properties: + interval: + description: 'OPTIONAL. The minimum amount of time in seconds that the + Wallet needs to wait before sending a new request to the Deferred + Credential Endpoint. Default value is 5 if not provided. + + ' + type: integer + type: object + required: + - error + title: Deferred Credential Error Response + type: object + DeferredCredentialRequest: + properties: + transaction_id: + description: 'REQUIRED. String identifying a Deferred Issuance transaction. The + Credential Issuer MUST invalidate the transaction_id after the Credential + for which it was meant has been obtained by the Wallet. + + ' + type: string + required: + - transaction_id + title: Deferred Credential Request + type: object + DeferredCredentialResponse: + properties: + c_nonce: + description: 'OPTIONAL. String containing a nonce to be used to create a + proof of possession of key material when requesting a Credential. The + Wallet MUST use this nonce value for its subsequent Credential Requests until + the Credential Issuer provides a fresh nonce. + + ' + type: string + c_nonce_expires_in: + description: 'OPTIONAL. Number denoting the lifetime in seconds of the c_nonce. + + ' + type: number + transaction_id: + description: 'OPTIONAL. String identifying a Deferred Issuance transaction. + This claim is contained in the response if the Credential Issuer was + unable to immediately issue the Credential. It MUST be present when the + credential parameter is not returned. It MUST be invalidated after the + Credential for which it was meant has been obtained by the Wallet. + + ' + type: string + required: + - transaction_id + title: Deferred Credential Response + type: object + IetfSdJwtVcCredentialConfiguration: + properties: + claims: + additionalProperties: true + description: 'OPTIONAL. Object containing a list of name/value pairs, where + each name identifies a claim about the subject offered in the Credential. The + value can be another such object (nested data structures), or an array + of such objects. + + ' + type: object + credential_signing_alg_values_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + algorithms that the Issuer uses to sign the issued Credential. + + ' + items: + type: string + type: array + cryptographic_binding_methods_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + representation of the cryptographic key material that the issued Credential + is bound to. Support for keys in JWK format [RFC7517] is indicated by + the value jwk. Support for keys expressed as a COSE Key object [RFC8152] + (for example, used in [ISO.18013-5]) is indicated by the value cose_key. + When the Cryptographic Binding Method is a DID, valid values are a did: + prefix followed by a method-name using a syntax as defined in Section + 3.1 of [DID-Core], but without a :and method-specific-id. For example, + support for the DID method with a method-name "example" would be represented + by did:example. + + ' + items: + type: string + type: array + display: + description: Array of objects, where each object contains the display properties + of the supported Credential for a certain language. + properties: + background_color: + description: 'OPTIONAL. String value of a background color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + background_image: + description: 'OPTIONAL. Object with information about the background + image of the Credential. + + ' + properties: + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the background image of the Credential from + the Credential Issuer. The Wallet needs to determine the scheme, + since the URI value could use the https: scheme, the data: scheme, + etc. + + ' + type: string + type: object + description: + description: 'OPTIONAL. String value of a description of the Credential. + + ' + type: string + locale: + description: 'OPTIONAL. String value that identifies the language of + this object represented as a language tag taken from values defined + in BCP47 [RFC5646]. Multiple display objects MAY be included for separate + languages. There MUST be only one object for each language identifier. + + ' + type: string + logo: + description: 'OPTIONAL. Object with information about the logo of the + Credential. + + ' + properties: + alt_text: + description: 'OPTIONAL. String value of the alternative text for + the logo image. + + ' + type: string + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the logo of the Credential from the Credential + Issuer. The Wallet needs to determine the scheme, since the URI + value could use the https: scheme, the data: scheme, etc + + ' + type: string + type: object + name: + description: REQUIRED. String value of a display name for the Credential. + type: string + text_color: + description: 'OPTIONAL. String value of a text color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + type: object + format: + description: 'REQUIRED. A JSON string identifying the format of this Credential, + i.e., `jwt_vc_json` or `mso_mdoc` or `vc+sd-jwt`. Depending on the format + value, the object contains further elements defining the type and (optionally) + particular claims the Credential MAY contain and information about how + to display the Credential. + + ' + type: string + order: + description: OPTIONAL. An array of the claim name values that lists them + in the order they should be displayed by the Wallet. + items: + type: string + type: array + proof_types_supported: + description: 'Object that describes specifics of the key proof(s) that the + Credential Issuer supports. This object contains a list of name/value + pairs, where each name is a unique identifier of the supported proof type(s). This + identifier is also used by the Wallet in the Credential Request. + + ' + properties: + proof_signing_alg_values_supported: + description: 'REQUIRED. Array of case sensitive strings that identify + the algorithms that the Issuer supports for this proof type. The + Wallet uses one of them to sign the proof. + + ' + items: + type: string + type: array + type: object + scope: + description: 'OPTIONAL. A JSON string identifying the scope value that this + Credential Issuer supports for this particular Credential. The value + can be the same across multiple credential_configurations_supported objects. The + Authorization Server MUST be able to uniquely identify the Credential + Issuer based on the scope value. The Wallet can use this value in the + Authorization Request. Scope values in this Credential Issuer metadata + MAY duplicate those in the `scopes_supported parameter` of the Authorization + Server. + + ' + type: string + vct: + description: REQUIRED. String designating the type of a Credential, as defined + in [I-D.ietf-oauth-sd-jwt-vc]. + type: string + required: + - vct + - format + title: IETF SD-JWT VC + type: object + InTimeCredentialErrorResponse: + properties: + error: + description: 'REQUIRED. The error parameter SHOULD be a single ASCII error + code from the following: + + ' + enum: + - invalid_credential_request + - unsupported_credential_type + - unsupported_credential_format + - invalid_proof + - invalid_encryption_parameters + type: string + error_description: + description: 'OPTIONAL. The error_description parameter MUST be a human-readable + ASCII text, providing any additional information used to assist the Client + implementers in understanding the occurred error. The values for the + error_description parameter MUST NOT include characters outside the set + %x20-21 / %x23-5B / %x5D-7E. + + ' + type: string + title: InTime Credential Error Response + type: object + InTimeCredentialResponse: + properties: + c_nonce: + description: 'OPTIONAL. String containing a nonce to be used to create a + proof of possession of key material when requesting a Credential. The + Wallet MUST use this nonce value for its subsequent Credential Requests until + the Credential Issuer provides a fresh nonce. + + ' + type: string + c_nonce_expires_in: + description: 'OPTIONAL. Number denoting the lifetime in seconds of the c_nonce. + + ' + type: number + credential: + description: OPTIONAL. Contains issued Credential. It MUST be present when + transaction_id is not returned. It MAY be a string or an object, depending + on the Credential format. + oneOf: + - type: string + - type: object + required: + - credential + title: InTime Credential Response + type: object + JwtVcJsonCredentialConfiguration: + properties: + credential_definition: + description: REQUIRED. Object containing the detailed description of the + Credential type. + properties: + credentialSubject: + additionalProperties: true + description: 'OPTIONAL. Object containing a list of name/value pairs, + where each name identifies a claim offered in the Credential. The + value can be another such object (nested data structures), or an array + of such objects. + + ' + type: object + type: + description: REQUIRED. Array designating the types a certain Credential + type supports, according to [VC_DATA], Section 4.3. + items: + type: string + type: array + type: object + credential_signing_alg_values_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + algorithms that the Issuer uses to sign the issued Credential. + + ' + items: + type: string + type: array + cryptographic_binding_methods_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + representation of the cryptographic key material that the issued Credential + is bound to. Support for keys in JWK format [RFC7517] is indicated by + the value jwk. Support for keys expressed as a COSE Key object [RFC8152] + (for example, used in [ISO.18013-5]) is indicated by the value cose_key. + When the Cryptographic Binding Method is a DID, valid values are a did: + prefix followed by a method-name using a syntax as defined in Section + 3.1 of [DID-Core], but without a :and method-specific-id. For example, + support for the DID method with a method-name "example" would be represented + by did:example. + + ' + items: + type: string + type: array + display: + description: Array of objects, where each object contains the display properties + of the supported Credential for a certain language. + properties: + background_color: + description: 'OPTIONAL. String value of a background color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + background_image: + description: 'OPTIONAL. Object with information about the background + image of the Credential. + + ' + properties: + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the background image of the Credential from + the Credential Issuer. The Wallet needs to determine the scheme, + since the URI value could use the https: scheme, the data: scheme, + etc. + + ' + type: string + type: object + description: + description: 'OPTIONAL. String value of a description of the Credential. + + ' + type: string + locale: + description: 'OPTIONAL. String value that identifies the language of + this object represented as a language tag taken from values defined + in BCP47 [RFC5646]. Multiple display objects MAY be included for separate + languages. There MUST be only one object for each language identifier. + + ' + type: string + logo: + description: 'OPTIONAL. Object with information about the logo of the + Credential. + + ' + properties: + alt_text: + description: 'OPTIONAL. String value of the alternative text for + the logo image. + + ' + type: string + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the logo of the Credential from the Credential + Issuer. The Wallet needs to determine the scheme, since the URI + value could use the https: scheme, the data: scheme, etc + + ' + type: string + type: object + name: + description: REQUIRED. String value of a display name for the Credential. + type: string + text_color: + description: 'OPTIONAL. String value of a text color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + type: object + format: + description: 'REQUIRED. A JSON string identifying the format of this Credential, + i.e., `jwt_vc_json` or `mso_mdoc` or `vc+sd-jwt`. Depending on the format + value, the object contains further elements defining the type and (optionally) + particular claims the Credential MAY contain and information about how + to display the Credential. + + ' + type: string + order: + description: OPTIONAL. Array of the claim name values that lists them in + the order they should be displayed by the Wallet. + items: + type: string + type: array + proof_types_supported: + description: 'Object that describes specifics of the key proof(s) that the + Credential Issuer supports. This object contains a list of name/value + pairs, where each name is a unique identifier of the supported proof type(s). This + identifier is also used by the Wallet in the Credential Request. + + ' + properties: + proof_signing_alg_values_supported: + description: 'REQUIRED. Array of case sensitive strings that identify + the algorithms that the Issuer supports for this proof type. The + Wallet uses one of them to sign the proof. + + ' + items: + type: string + type: array + type: object + scope: + description: 'OPTIONAL. A JSON string identifying the scope value that this + Credential Issuer supports for this particular Credential. The value + can be the same across multiple credential_configurations_supported objects. The + Authorization Server MUST be able to uniquely identify the Credential + Issuer based on the scope value. The Wallet can use this value in the + Authorization Request. Scope values in this Credential Issuer metadata + MAY duplicate those in the `scopes_supported parameter` of the Authorization + Server. + + ' + type: string + required: + - credential_definition + - format + title: VC Signed as a JWT + type: object + MsoMdocCredentialConfiguration: + properties: + claims: + additionalProperties: true + description: 'OPTIONAL. Object containing a list of name/value pairs, where + the name is a certain namespace as defined in [ISO.18013-5] (or any profile + of it), and the value is an object. This object also contains a list + of name/value pairs, where the name is a claim name value that is defined + in the respective namespace and is offered in the Credential. + + ' + type: object + credential_signing_alg_values_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + algorithms that the Issuer uses to sign the issued Credential. + + ' + items: + type: string + type: array + cryptographic_binding_methods_supported: + description: 'OPTIONAL. Array of case sensitive strings that identify the + representation of the cryptographic key material that the issued Credential + is bound to. Support for keys in JWK format [RFC7517] is indicated by + the value jwk. Support for keys expressed as a COSE Key object [RFC8152] + (for example, used in [ISO.18013-5]) is indicated by the value cose_key. + When the Cryptographic Binding Method is a DID, valid values are a did: + prefix followed by a method-name using a syntax as defined in Section + 3.1 of [DID-Core], but without a :and method-specific-id. For example, + support for the DID method with a method-name "example" would be represented + by did:example. + + ' + items: + type: string + type: array + display: + description: Array of objects, where each object contains the display properties + of the supported Credential for a certain language. + properties: + background_color: + description: 'OPTIONAL. String value of a background color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + background_image: + description: 'OPTIONAL. Object with information about the background + image of the Credential. + + ' + properties: + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the background image of the Credential from + the Credential Issuer. The Wallet needs to determine the scheme, + since the URI value could use the https: scheme, the data: scheme, + etc. + + ' + type: string + type: object + description: + description: 'OPTIONAL. String value of a description of the Credential. + + ' + type: string + locale: + description: 'OPTIONAL. String value that identifies the language of + this object represented as a language tag taken from values defined + in BCP47 [RFC5646]. Multiple display objects MAY be included for separate + languages. There MUST be only one object for each language identifier. + + ' + type: string + logo: + description: 'OPTIONAL. Object with information about the logo of the + Credential. + + ' + properties: + alt_text: + description: 'OPTIONAL. String value of the alternative text for + the logo image. + + ' + type: string + uri: + description: 'REQUIRED. String value that contains a URI where the + Wallet can obtain the logo of the Credential from the Credential + Issuer. The Wallet needs to determine the scheme, since the URI + value could use the https: scheme, the data: scheme, etc + + ' + type: string + type: object + name: + description: REQUIRED. String value of a display name for the Credential. + type: string + text_color: + description: 'OPTIONAL. String value of a text color of the Credential + represented as numerical color values defined in CSS Color Module + Level 37 [CSS-Color]. + + ' + type: string + type: object + doctype: + description: REQUIRED. String identifying the Credential type, as defined + in [ISO.18013-5]. + type: string + format: + description: 'REQUIRED. A JSON string identifying the format of this Credential, + i.e., `jwt_vc_json` or `mso_mdoc` or `vc+sd-jwt`. Depending on the format + value, the object contains further elements defining the type and (optionally) + particular claims the Credential MAY contain and information about how + to display the Credential. + + ' + type: string + order: + description: OPTIONAL. Array of namespaced claim name values that lists + them in the order they should be displayed by the Wallet. The values MUST + be two strings separated by a tilde ('~') character, where the first string + is a namespace value and a second is a claim name value. For example, + `org.iso.18013.5.1~given_name". + items: + type: string + type: array + proof_types_supported: + description: 'Object that describes specifics of the key proof(s) that the + Credential Issuer supports. This object contains a list of name/value + pairs, where each name is a unique identifier of the supported proof type(s). This + identifier is also used by the Wallet in the Credential Request. + + ' + properties: + proof_signing_alg_values_supported: + description: 'REQUIRED. Array of case sensitive strings that identify + the algorithms that the Issuer supports for this proof type. The + Wallet uses one of them to sign the proof. + + ' + items: + type: string + type: array + type: object + scope: + description: 'OPTIONAL. A JSON string identifying the scope value that this + Credential Issuer supports for this particular Credential. The value + can be the same across multiple credential_configurations_supported objects. The + Authorization Server MUST be able to uniquely identify the Credential + Issuer based on the scope value. The Wallet can use this value in the + Authorization Request. Scope values in this Credential Issuer metadata + MAY duplicate those in the `scopes_supported parameter` of the Authorization + Server. + + ' + type: string + required: + - doctype + - format + title: ISO mDL + type: object + PreAuthorisedCodeGrant: + properties: + urn:ietf:params:oauth:grant-type:pre-authorized_code: + description: Pre-authorised code grant + properties: + pre-authorized_code: + description: '"REQUIRED. The code representing the Credential Issuer''s + authorization for the Wallet to obtain Credentials of a certain type. This + code MUST be short lived and single use. If the Wallet decides to + use the Pre-Authorized Code Flow, this parameter value MUST be included + in the subsequent Token Request with the Pre-Authorized Code Flow." + + ' + type: string + tx_code: + description: '"OPTIONAL. Object specifying whether the Authorization + Server expects presentation of a Transaction Code by the End-User + along with the Token Request in a Pre-Authorized Code Flow. If the + Authorization Server does not expect a Transaction Code, this object + is absent; this is the default. The Transaction Code is intended + to bind the Pre-Authorized Code to a certain transaction to prevent + replay of this code by an attacker that, for example, scanned the + QR code while standing behind the legitimate End-User. It is RECOMMENDED + to send the Transaction Code via a separate channel. If the Wallet + decides to use the Pre-Authorized Code Flow, the Transaction Code + value MUST be sent in the tx_code parameter with the respective Token + Request. If no length or description is given, this object may be + empty, indicating that a Transaction Code is required." + + ' + properties: + description: + description: '"OPTIONAL. String containing guidance for the Holder + of the Wallet on how to obtain the Transaction Code, e.g., describing + over which communication channel it is delivered. The Wallet + is RECOMMENDED to display this description next to the Transaction + Code input screen to improve the user experience. The length + of the string MUST NOT exceed 300 characters. The description + does not support internationalization, however the Issuer MAY + detect the Holder''s language by previous communication or an + HTTP Accept-Language header within an HTTP GET request for a Credential + Offer URI." + + ' + type: string + input_mode: + description: '"OPTIONAL. String specifying the input character set. Possible + values are numeric (only digits) and text (any characters). The + default is numeric." + + ' + type: string + length: + description: '"OPTIONAL. Integer specifying the length of the Transaction + Code. This helps the Wallet to render the input screen and improve + the user experience." + + ' + type: integer + type: object + type: object + required: + - urn:ietf:params:oauth:grant-type:pre-authorized_code + title: Pre-authorised code grant + type: object + PreAuthorisedCodeTokenRequest: + properties: + grant_type: + description: 'The type of grant being used. For pre-authorized code flow, + this must be set to ''urn:ietf:params:oauth:grant-type:pre-authorized_code''. + + ' + example: urn:ietf:params:oauth:grant-type:pre-authorized_code + type: string + pre-authorized_code: + description: 'The pre-authorized code issued by the authorization server. + This code is used to request an access token. + + ' + type: string + tx_code: + description: 'A transaction code that helps identify and correlate the request + with the original authorization transaction. + + ' + type: string + required: + - grant_type + - pre-authorized_code + - tx_code + title: Token Request (for pre-authorised code grant) + type: object + TokenErrorResponse: + properties: + authorization_pending: + description: 'OPTIONAL. Detailed description for authorization_pending errors. + + ' + properties: + interval: + description: 'OPTIONAL. The minimum amount of time in seconds that the + Wallet needs to wait before sending a new request. Default value + is 5 if not provided. + + ' + type: integer + type: object + error: + description: 'REQUIRED. Error code indicating the type of error that occurred. + + ' + enum: + - invalid_request + - invalid_grant + - invalid_client + - authorization_pending + - slow_down + type: string + error_description: + description: 'OPTIONAL. Human-readable ASCII text providing additional information about + the error. + + ' + type: string + slow_down: + description: 'OPTIONAL. Detailed description for slow_down errors. + + ' + properties: + interval_increase: + description: 'OPTIONAL. The amount of time in seconds by which to increase + the polling interval. + + ' + type: integer + type: object + required: + - error + title: Token Error Response + type: object + TokenResponse: + properties: + access_token: + description: 'The access token issued by the authorization server. This + token is used to access protected resources. + + ' + example: eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ + type: string + authorization_details: + items: + properties: + credential_configuration_id: + description: 'REQUIRED when `authorization_details` is used. The identifier + for the Credential configuration. + + ' + example: UniversityDegreeCredential + type: string + credential_identifiers: + description: 'OPTIONAL. An array of strings, each uniquely identifying + a Credential that can be issued using the Access Token. These Credentials + correspond to the same entry in the `credential_configurations_supported` + Credential Issuer metadata but may contain different claims or subsets + of claims. + + ' + example: + - CivilEngineeringDegree-2023 + - ElectricalEngineeringDegree-2023 + items: + type: string + type: array + type: + description: 'REQUIRED. The type of authorization details. Must be + "openid_credential" when used to request issuance of a specific + Credential type. + + ' + example: openid_credential + type: string + type: object + type: array + c_nonce: + description: 'OPTIONAL. A nonce used when creating proof of possession of + the key proof. This nonce must be used for subsequent requests until a + fresh nonce is provided by the Credential Issuer. + + ' + example: tZignsnFbp + type: string + c_nonce_expires_in: + description: 'OPTIONAL. The lifetime in seconds of the `c_nonce`. After + this time, the `c_nonce` will expire. + + ' + example: 86400 + type: integer + expires_in: + description: 'The lifetime in seconds of the access token. After this time, + the token will expire and a new one must be requested. + + ' + example: 86400 + type: integer + token_type: + description: 'The type of the token issued. Typically this is "bearer". + + ' + example: bearer + type: string + required: + - access_token + - token_type + - expires_in + title: Token Response + type: object +info: + contact: + name: EWC + url: https://eudiwalletconsortium.org/ + description: The EWC APIs are defined to be used across all wallet providers within + EWC according the [EWC RFCs](https://github.com/EWC-consortium/eudi-wallet-rfcs). + license: + name: Apache 2.0 + url: https://www.apache.org/licenses/LICENSE-2.0 + title: EWC APIs + version: 2024.3.1 +openapi: 3.0.0 +paths: + /rfc001/.well-known/oauth-authorization-server: + get: + description: The Authorization server metadata contain metadata describing authorisation + server configuration. + operationId: authorisationServerMetadata + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AuthorisationServerMetadata' + description: '' + summary: Authorisation Server Metadata + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/.well-known/openid-credential-issuer: + get: + description: The credential issuer metdata contains information on the Credential + Issuer's technical capabilities, supported Credentials, and (internationalized) + display information. + operationId: credentialIssuerMetadata + responses: + '200': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/CredentialIssuerMetadata' + description: '' + summary: Credential Issuer Metadata + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/authorize: + get: + description: 'An Authorization Request is an OAuth 2.0 Authorization Request + as defined in Section 4.1.1 of [RFC6749]. This request seeks to obtain authorization + to access the Credential Endpoint. + + ' + operationId: authorizationRequest + parameters: + - description: 'REQUIRED. The value must be "code" to request an authorization + code. + + ' + in: query + name: response_type + required: true + schema: + type: string + - description: 'REQUIRED. The identifier for the client making the request. + + ' + in: query + name: client_id + required: true + schema: + type: string + - description: 'REQUIRED. The code challenge used for Proof Key for Code Exchange + (PKCE) as specified in OAuth 2.0 for Public Clients. + + ' + in: query + name: code_challenge + required: true + schema: + type: string + - description: 'OPTIONAL. The method used to transform the code verifier. Defaults + to "plain" if not present. Possible values are "S256" or "plain", as defined + in PKCE for OAuth 2.0. + + ' + in: query + name: code_challenge_method + required: true + schema: + type: string + - description: 'OPTIONAL. Provides fine-grained access details as specified + in the OAuth 2.0 Rich Authorization Requests specification. The `authorization_details` + parameter, as defined in Section 2 of [RFC9396], should be used to convey + the specifics of the Credentials the Wallet intends to obtain. This specification + introduces a new authorization details type, `openid_credential`. + + ' + in: query + name: authorization_details + required: false + schema: + type: string + - description: 'OPTIONAL. The redirection endpoint where the authorization server + will send the user-agent after authorization is complete. + + ' + in: query + name: redirect_uri + required: false + schema: + type: string + - description: 'OPTIONAL. Defines the scope of the Credential request. If the + Wallet is unsure of the scope value, it can discover it using the ''scope'' + parameter from the Credential Issuer''s metadata. This parameter assists + in identifying the appropriate Credential configuration based on the Credential + Offer or other parameters from the Credential Issuer. + + ' + in: query + name: scope + required: false + schema: + type: string + - description: 'OPTIONAL. A string value representing a specific processing + context at the Credential Issuer. This value is usually provided in a Credential + Offer from the Credential Issuer to the Wallet and is used to pass the issuer_state + value back to the Credential Issuer. + + ' + in: query + name: issuer_state + required: false + schema: + type: string + responses: + '302': + description: Authorization Response + headers: + Location: + description: 'The URL to which the user-agent will be redirected after + successful authorization. This URL contains the authorization code + and any additional parameters as specified in the OAuth 2.0 Authorization + framework. + + ' + schema: + type: string + summary: Authorization Request + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/credential: + post: + requestBody: + content: + application/json: + schema: + oneOf: + - properties: + credential_definition: + description: 'REQUIRED when the format parameter is present in + the Credential Request. MUST NOT be used otherwise. Contains + the detailed description of the Credential type. + + ' + properties: + credentialSubject: + description: 'OPTIONAL. + + ' + type: object + type: + description: 'REQUIRED. The credential issued by the Credential + Issuer MUST contain at least the values listed in this claim. + + ' + type: array + type: object + format: + description: 'REQUIRED when the credential_identifiers parameter + was not returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + proof: + description: 'OPTIONAL. Contains the proof of possession of the + cryptographic key material the issued Credential would be bound + to. + + ' + properties: + credential_identifier: + description: 'REQUIRED when credential_identifiers parameter + was returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + credential_response_encryption: + description: 'OPTIONAL. Contains information for encrypting + the Credential Response. + + ' + properties: + alg: + description: 'REQUIRED. JWE alg algorithm for encrypting + Credential Responses. + + ' + type: string + enc: + description: 'REQUIRED. JWE enc algorithm for encrypting + Credential Responses. + + ' + type: string + jwk: + description: 'REQUIRED. Contains a single public key as + a JWK used for encrypting the Credential Response. + + ' + type: object + type: object + proof_type: + description: 'REQUIRED. Denotes the key proof type. + + ' + type: string + type: object + required: + - proof_type + - jwk + - alg + - enc + - credential_definition + title: Credential Request (VC Signed as a JWT) + type: object + - properties: + claims: + description: 'OPTIONAL. + + ' + type: object + doctype: + description: 'REQUIRED when the format parameter is present in + the Credential Request. MUST NOT be used otherwise. The Credential + issued by the Credential Issuer MUST contain at least the values + listed in this claim. + + ' + type: string + format: + description: 'REQUIRED when the credential_identifiers parameter + was not returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + proof: + description: 'OPTIONAL. Contains the proof of possession of the + cryptographic key material the issued Credential would be bound + to. + + ' + properties: + credential_identifier: + description: 'REQUIRED when credential_identifiers parameter + was returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + credential_response_encryption: + description: 'OPTIONAL. Contains information for encrypting + the Credential Response. + + ' + properties: + alg: + description: 'REQUIRED. JWE alg algorithm for encrypting + Credential Responses. + + ' + type: string + enc: + description: 'REQUIRED. JWE enc algorithm for encrypting + Credential Responses. + + ' + type: string + jwk: + description: 'REQUIRED. Contains a single public key as + a JWK used for encrypting the Credential Response. + + ' + type: object + type: object + proof_type: + description: 'REQUIRED. Denotes the key proof type. + + ' + type: string + type: object + required: + - proof_type + - jwk + - alg + - enc + - doctype + title: Credential Request (ISO mDL) + type: object + - properties: + claims: + description: 'OPTIONAL. + + ' + type: object + format: + description: 'REQUIRED when the credential_identifiers parameter + was not returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + proof: + description: 'OPTIONAL. Contains the proof of possession of the + cryptographic key material the issued Credential would be bound + to. + + ' + properties: + credential_identifier: + description: 'REQUIRED when credential_identifiers parameter + was returned from the Token Response. MUST NOT be used otherwise. + + ' + type: string + credential_response_encryption: + description: 'OPTIONAL. Contains information for encrypting + the Credential Response. + + ' + properties: + alg: + description: 'REQUIRED. JWE alg algorithm for encrypting + Credential Responses. + + ' + type: string + enc: + description: 'REQUIRED. JWE enc algorithm for encrypting + Credential Responses. + + ' + type: string + jwk: + description: 'REQUIRED. Contains a single public key as + a JWK used for encrypting the Credential Response. + + ' + type: object + type: object + proof_type: + description: 'REQUIRED. Denotes the key proof type. + + ' + type: string + type: object + vct: + description: 'REQUIRED when the format parameter is present in + the Credential Request. MUST NOT be used otherwise. This claim + contains the type value of the Credential that the Wallet requests + the Credential Issuer to issue. + + ' + type: string + required: + - proof_type + - jwk + - alg + - enc + - vct + title: Credential Request (IETF SD-JWT VC) + type: object + required: true + responses: + '200': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/InTimeCredentialResponse' + - $ref: '#/components/schemas/DeferredCredentialResponse' + description: Credential response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/InTimeCredentialErrorResponse' + description: Error Response + summary: Credential Request + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/credential_offer: + get: + description: The Credential Issuer sends Credential Offer using an HTTP GET + request or an HTTP redirect to the Wallet's Credential Offer Endpoint + operationId: credentialOfferEndpoint + parameters: + - description: Object with the Credential Offer parameters. This MUST NOT be + present when the `credential_offer_uri` parameter is present. + in: query + name: credential_offer + schema: + type: string + - description: String that is a URL using the https scheme referencing a resource + containing a JSON object with the Credential Offer parameters. This MUST + NOT be present when the `credential_offer` parameter is present. + in: query + name: credential_offer_uri + schema: + type: string + responses: + '200': + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/CredentialOffer' + description: '' + summary: Credential Offer Endpoint + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/deferred_credential: + post: + parameters: + - description: Acceptance token from the credential response + in: header + name: Authorization + schema: + example: Bearer eyJ0eXAi...KTjcrDMg + type: string + requestBody: + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/DeferredCredentialRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/InTimeCredentialResponse' + description: Credential response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/DeferredCredentialErrorResponse' + description: Error Response + summary: Deferred Credential Request + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/par: + post: + description: 'A Pushed Authorization Request (PAR) allows a client to submit + its authorization request parameters to the authorization server in advance, + providing a PAR endpoint that stores these parameters and returns a request + URI. The client then uses this URI in its authorization request to the authorization + endpoint. This method enhances security by avoiding the exposure of sensitive + parameters in the URL. + + ' + operationId: pushedAuthorizationRequest + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + authorization_details: + description: 'OPTIONAL. Provides fine-grained access details as + specified in the OAuth 2.0 Rich Authorization Requests specification. + The `authorization_details` parameter, as defined in Section 2 + of [RFC9396], should be used to convey the specifics of the Credentials + the Wallet intends to obtain. This specification introduces a + new authorization details type, `openid_credential`. + + ' + type: string + client_id: + description: 'REQUIRED. The identifier for the client making the + request. + + ' + type: string + code_challenge: + description: 'REQUIRED. The code challenge used for Proof Key for + Code Exchange (PKCE) as specified in OAuth 2.0 for Public Clients. + + ' + type: string + code_challenge_method: + description: 'OPTIONAL. The method used to transform the code verifier. + Defaults to "plain" if not present. Possible values are "S256" + or "plain", as defined in PKCE for OAuth 2.0. + + ' + type: string + issuer_state: + description: 'OPTIONAL. A string value representing a specific processing + context at the Credential Issuer. This value is usually provided + in a Credential Offer from the Credential Issuer to the Wallet + and is used to pass the issuer_state value back to the Credential + Issuer. + + ' + type: string + redirect_uri: + description: 'OPTIONAL. The redirection endpoint where the authorization + server will send the user-agent after authorization is complete. + + ' + type: string + response_type: + description: 'REQUIRED. The value must be "code" to request an authorization + code. + + ' + type: string + scope: + description: 'OPTIONAL. Defines the scope of the Credential request. + If the Wallet is unsure of the scope value, it can discover it + using the ''scope'' parameter from the Credential Issuer''s metadata. + This parameter assists in identifying the appropriate Credential + configuration based on the Credential Offer or other parameters + from the Credential Issuer. + + ' + type: string + required: + - response_type + - client_id + - code_challenge + - code_challenge_method + type: object + required: true + responses: + '201': + content: + application/json: + schema: + properties: + expires_in: + description: 'The lifetime in seconds of the request_uri. After + this time, the request_uri will no longer be valid. + + ' + type: integer + request_uri: + description: 'The URI that can be used in an authorization request + to reference the parameters submitted in the PAR request. + + ' + type: string + type: object + description: Pushed Authorization Request Created + summary: Pushed Authorization Request + tags: + - 'EWC RFC001: Issue Verifiable Credential' + /rfc001/token: + post: + requestBody: + content: + application/x-www-form-urlencoded: + schema: + oneOf: + - properties: + code: + description: 'The authorization code received from the authorization + server in response to the authorization request. This code is + used to obtain an access token from the token endpoint. + + ' + type: string + code_verifier: + description: 'The code verifier used in Proof Key for Code Exchange + (PKCE). This is included in the token request to validate the + code challenge that was used in the initial authorization request. + + ' + type: string + grant_type: + description: 'The type of the grant being used. For example, ''authorization_code'' + indicates that the authorization code grant type is being used. + + ' + example: authorization_code + type: string + redirect_uri: + description: 'The redirect URI used in the authorization request. + It must match the redirect URI used during the authorization + request to ensure consistency and prevent attacks. + + ' + type: string + required: + - grant_type + - code + title: Token Request (for authorisation code grant) + type: object + - $ref: '#/components/schemas/PreAuthorisedCodeTokenRequest' + required: true + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenResponse' + description: Token response + '400': + content: + application/json: + schema: + $ref: '#/components/schemas/TokenErrorResponse' + description: Error Response + summary: Token request + tags: + - 'EWC RFC001: Issue Verifiable Credential' +servers: +- description: Example server + url: https://example.com +tags: +- description: 'This consists of endpoints for EWC RFC001: Issue Verifiable Credential' + name: 'EWC RFC001: Issue Verifiable Credential' +- description: 'This consists of endpoints for EWC RFC002: Present Verifiable Credential' + name: 'EWC RFC002: Present Verifiable Credential' \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f1fc4d2..9d08ebb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,11 @@ "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", "@sd-jwt/core": "^0.6.1", - "@sd-jwt/crypto-nodejs": "^0.6.1", - "@sd-jwt/sd-jwt-vc": "^0.6.1", + "@sd-jwt/crypto-nodejs": "^0.7.2", + "@sd-jwt/sd-jwt-vc": "^0.7.2", + "base64url": "^3.0.1", "express": "^4.18.3", + "express-openapi-validator": "^5.3.7", "image-data-uri": "^2.0.1", "jose": "^5.2.3", "jsonwebtoken": "^9.0.2", @@ -23,11 +25,32 @@ "waltid-sd-jwt": "^1.2311200231.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.0.tgz", + "integrity": "sha512-pRrmXMCwnmrkS3MLgAIW5dXRzeTv6GLjkjb4HmxNnvAKXN1Nfzp4KmGADBQvlVUcqi+a5D+hfGDLLnd5NnYxog==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@jorgeferrero/stream-to-buffer": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@jorgeferrero/stream-to-buffer/-/stream-to-buffer-2.0.6.tgz", "integrity": "sha512-G7jiBkMwDdYADntdV/qzl9H4Lkch0RyektqXl5+KhktduP4/rE1RaTz0wKZFdC2dKo3S0JaxoUUC3uDeXmI7EQ==" }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@sd-jwt/core": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.6.1.tgz", @@ -43,11 +66,11 @@ } }, "node_modules/@sd-jwt/crypto-nodejs": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@sd-jwt/crypto-nodejs/-/crypto-nodejs-0.6.1.tgz", - "integrity": "sha512-dkzZYNPlswCCnn4PfR/+GFeo/tI7QEZv4vqzpAp8/7v62Cpi/VjdyMmW5+SlHg3sTw82eL/B9aFTcFlnbQuvPA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/crypto-nodejs/-/crypto-nodejs-0.7.2.tgz", + "integrity": "sha512-7DHy1WBHwvXseiX+U7XA6jX4dX4Ins3Nxd12JhBSm+FJfIwU97FU/H0KlF6lLyi4a4nbY/O6U9wJjYI1PxA9sQ==", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@sd-jwt/decode": { @@ -62,6 +85,27 @@ "node": ">=16" } }, + "node_modules/@sd-jwt/jwt-status-list": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/jwt-status-list/-/jwt-status-list-0.7.2.tgz", + "integrity": "sha512-o/Mg/Zg21poFsPXuxtPD9sdXq2b/0L+rb9gxU2k1rp1aT+DWmqD0k8v0Ttr2tlMc8l1xXQNA8FLXbL1AdLRmbQ==", + "dependencies": { + "@sd-jwt/types": "0.7.2", + "base64url": "^3.0.1", + "pako": "^2.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sd-jwt/jwt-status-list/node_modules/@sd-jwt/types": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/types/-/types-0.7.2.tgz", + "integrity": "sha512-1NRKowiW0ZiB9SGLApLPBH4Xk8gDQJ+nA9NdZ+uy6MmJKLEwjuJxO7yTvRIv/jX/0/Ebh339S7Kq4RD2AiFuRg==", + "engines": { + "node": ">=18" + } + }, "node_modules/@sd-jwt/present": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/@sd-jwt/present/-/present-0.6.1.tgz", @@ -76,14 +120,75 @@ } }, "node_modules/@sd-jwt/sd-jwt-vc": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.6.1.tgz", - "integrity": "sha512-eF7NAFvedBCx+vrw4TVY3evUz5rAG8/FtB/CUudYEigKcpanLgfuNGhk93D45k+lLDG0b24w+qorqbpLZzHA2g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/sd-jwt-vc/-/sd-jwt-vc-0.7.2.tgz", + "integrity": "sha512-rryYmnoJHRSNqHcrs0Atta+bfJzU2yT7mYumR2D4lTfxJKWZd0OHHFq57uZSEm/wXPI6uytUJXYbEboCqLUAtw==", "dependencies": { - "@sd-jwt/core": "0.6.1" + "@sd-jwt/core": "0.7.2", + "@sd-jwt/jwt-status-list": "0.7.2", + "@sd-jwt/utils": "0.7.2" }, "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/@sd-jwt/core": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/core/-/core-0.7.2.tgz", + "integrity": "sha512-vix1GplUFc1A9H42r/yXkg7cKYthggyqZEwlFdsBbn4xdZNE+AHVF4N7kPa1pPxipwN3UIHd4XnQ5MJV15mhsQ==", + "dependencies": { + "@sd-jwt/decode": "0.7.2", + "@sd-jwt/present": "0.7.2", + "@sd-jwt/types": "0.7.2", + "@sd-jwt/utils": "0.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/@sd-jwt/decode": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/decode/-/decode-0.7.2.tgz", + "integrity": "sha512-dan2LSvK63SKwb62031G4r7TE4TaiI0EK1KbPXqS+LCXNkNDUHqhtYp9uOpj+grXceCsMtMa2f8VnUfsjmwHHg==", + "dependencies": { + "@sd-jwt/types": "0.7.2", + "@sd-jwt/utils": "0.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/@sd-jwt/present": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/present/-/present-0.7.2.tgz", + "integrity": "sha512-mQV85u2+mLLy2VZ9Wx2zpaB6yTDnbhCfWkP7eeCrzJQHBKAAHko8GrylEFmLKewFIcajS/r4lT/zHOsCkp5pZw==", + "dependencies": { + "@sd-jwt/decode": "0.7.2", + "@sd-jwt/types": "0.7.2", + "@sd-jwt/utils": "0.7.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/@sd-jwt/types": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/types/-/types-0.7.2.tgz", + "integrity": "sha512-1NRKowiW0ZiB9SGLApLPBH4Xk8gDQJ+nA9NdZ+uy6MmJKLEwjuJxO7yTvRIv/jX/0/Ebh339S7Kq4RD2AiFuRg==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@sd-jwt/sd-jwt-vc/node_modules/@sd-jwt/utils": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sd-jwt/utils/-/utils-0.7.2.tgz", + "integrity": "sha512-aMPY7uHRMgyI5PlDvEiIc+eBFGC1EM8OCQRiEjJ8HGN0pajWMYj0qwSw7pS90A49/DsYU1a5Zpvb7nyjgGH0Yg==", + "dependencies": { + "@sd-jwt/types": "0.7.2", + "js-base64": "^3.7.6" + }, + "engines": { + "node": ">=18" } }, "node_modules/@sd-jwt/types": { @@ -106,6 +211,105 @@ "node": ">=16" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "license": "MIT", @@ -132,6 +336,42 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/ansi-escape-sequences": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-4.1.0.tgz", @@ -159,6 +399,16 @@ "node": ">=4" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/array-back": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-2.0.0.tgz", @@ -233,7 +483,8 @@ }, "node_modules/base64url": { "version": "3.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", "engines": { "node": ">=6.0.0" } @@ -304,6 +555,22 @@ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -391,6 +658,20 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "license": "MIT", @@ -596,6 +877,78 @@ "node": ">= 0.10.0" } }, + "node_modules/express-openapi-validator": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/express-openapi-validator/-/express-openapi-validator-5.3.7.tgz", + "integrity": "sha512-AFlIXvICrPWJvWtrsfpL212kyvmyzThcZ1ASnnsRqmzxSo/3SV+J7M1oEsFYbqo7AYQPGtSBKy7eheGM6wPwag==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.7.0", + "@types/multer": "^1.4.12", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^2.1.1", + "content-type": "^1.0.5", + "json-schema-traverse": "^1.0.0", + "lodash.clonedeep": "^4.5.0", + "lodash.get": "^4.4.2", + "media-typer": "^1.1.0", + "multer": "^1.4.5-lts.1", + "ono": "^7.1.3", + "path-to-regexp": "^8.1.0" + }, + "peerDependencies": { + "express": "*" + } + }, + "node_modules/express-openapi-validator/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/express-openapi-validator/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/express-openapi-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/express-openapi-validator/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-openapi-validator/node_modules/path-to-regexp": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -619,6 +972,11 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, "node_modules/finalhandler": { "version": "1.2.0", "license": "MIT", @@ -1065,6 +1423,11 @@ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1083,6 +1446,17 @@ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -1194,6 +1568,16 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -1316,10 +1700,46 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.0.0", "license": "MIT" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "license": "MIT", @@ -1357,6 +1777,14 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "license": "MIT", @@ -1436,6 +1864,14 @@ "wrappy": "1" } }, + "node_modules/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-9jnfVriq7uJM4o5ganUY54ntUm+5EK21EGaQ5NWnkWg3zz5ywbbonlBguRcnmF1/HDiIe3zxNxXcO1YPBmPcQQ==", + "dependencies": { + "@jsdevtools/ono": "7.1.3" + } + }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -1514,6 +1950,11 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -1576,6 +2017,25 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/reduce-flatten": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", @@ -1632,6 +2092,14 @@ "uuid": "bin/uuid" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -1789,6 +2257,27 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/stringify-parameters": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stringify-parameters/-/stringify-parameters-0.0.4.tgz", @@ -1898,11 +2387,21 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typical": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/unpack-string": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/unpack-string/-/unpack-string-0.0.2.tgz", @@ -1923,6 +2422,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "license": "MIT", @@ -1996,6 +2500,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 48685d2..0f05b1c 100644 --- a/package.json +++ b/package.json @@ -6,16 +6,18 @@ "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "dev": "SERVER_URL=https://a6bf-2a02-587-870d-e900-8ba6-393a-385b-5730.ngrok-free.app node server.js" + "dev": "SERVER_URL=https://cef1-2a02-587-8704-c700-f7b8-b2c7-d0e1-cf37.ngrok-free.app node server.js" }, "author": "", "license": "ISC", "dependencies": { "@jorgeferrero/stream-to-buffer": "^2.0.6", "@sd-jwt/core": "^0.6.1", - "@sd-jwt/crypto-nodejs": "^0.6.1", - "@sd-jwt/sd-jwt-vc": "^0.6.1", + "@sd-jwt/crypto-nodejs": "^0.7.2", + "@sd-jwt/sd-jwt-vc": "^0.7.2", + "base64url": "^3.0.1", "express": "^4.18.3", + "express-openapi-validator": "^5.3.7", "image-data-uri": "^2.0.1", "jose": "^5.2.3", "jsonwebtoken": "^9.0.2", diff --git a/routes/codeFlowJwtRoutes.js b/routes/codeFlowJwtRoutes.js index 83b8c59..01e1fa8 100644 --- a/routes/codeFlowJwtRoutes.js +++ b/routes/codeFlowJwtRoutes.js @@ -1,7 +1,7 @@ import express from "express"; import fs from "fs"; import { v4 as uuidv4 } from "uuid"; -import { generateNonce, buildVpRequestJwt } from "../utils/cryptoUtils.js"; +import { generateNonce, buildVpRequestJSON } from "../utils/cryptoUtils.js"; import { getAuthCodeSessions } from "../services/cacheService.js"; import qr from "qr-image"; @@ -57,150 +57,11 @@ codeFlowRouter.get(["/credential-offer-code/:id"], (req, res) => { }); }); -codeFlowRouter.get("/authorize", async (req, res) => { - const responseType = req.query.response_type; - const scope = req.query.scope; - const issuerState = decodeURIComponent(req.query.issuer_state); // This can be associated with the ITB session - const state = req.query.state; - const clientId = decodeURIComponent(req.query.client_id); //DID of the holder requesting the credential - let authorizationDetails = ""; - try { - authorizationDetails = decodeURIComponent(req.query.authorization_details); //TODO this contains the credentials requested - } catch (error) { - console.log( - "No credentials requested! req.query.authorization_details missing!" - ); - errors.push( - "No credentials requested! req.query.authorization_details missing!" - ); - } - - const redirectUri = decodeURIComponent(req.query.redirect_uri); - const nonce = req.query.nonce; - const codeChallenge = decodeURIComponent(req.query.code_challenge); - const codeChallengeMethod = req.query.code_challenge_method; //this should equal to S256 - try { - const clientMetadata = JSON.parse( - decodeURIComponent(req.query.client_metadata) - ); - } catch (error) { - console.log("client_metadata was missing"); - console.log(error); - } - - //validations - let errors = []; - /* - [{"format":"jwt_vc", - "locations":["https://issuer.example.com"], - "type":"openid_credential", - "types":["VerifiableCredential","VerifiableAttestation","VerifiablePortableDocumentA1"]}] - */ - if (!authorizationDetails) { - errors.push("no credentials requested"); - console.log(`no credentials requested`); - } else { - try{ - if (authorizationDetails.credential_definition) { - console.log( - `credential ${authorizationDetails.credential_definition.type} was requested` - ); - } else if (authorizationDetails.types) { - //EBSI style - console.log(`credential ${authorizationDetails.types} was requested`); - } - }catch(error){ - errors.push("no credentials requested"); - } - - } - - if (responseType !== "code") { - errors.push("Invalid response_type"); - } - if (!scope.includes("openid")) { - errors.push("Invalid scope"); - } - // If validations pass, redirect with a 302 Found response - const authorizationCode = null; //"SplxlOBeZQQYbYS6WxSbIA"; - const codeSessions = getAuthCodeSessions(); - if (codeSessions.sessions.indexOf(issuerState) >= 0) { - codeSessions.requests.push({ - challenge: codeChallenge, - method: codeChallengeMethod, - sessionId: authorizationCode, - issuerState: issuerState, - state: state, - }); - codeSessions.results.push({ - sessionId: authorizationCode, - issuerState: issuerState, - state: state, - status: "pending", - }); - codeSessions.walletSessions.push(state); // push state as send by wallet - } else { - console.log("ITB session not found"); - } - // codeSessions.sessions.push(issuerState); - - // for normal response not requesting VP from wallet - //const redirectUrl = `${redirectUri}?code=${authorizationCode}&state=${state}`; - - //5.1.5. Dynamic Credential Request https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#name-successful-authorization-re - const vpRequestJWT = buildVpRequestJwt( - state, - nonce, - clientId, - "response_uri", - null, - "jwk", - serverURL, - privateKey - ); - const redirectUrl = `http://localhost:8080?state=${state}&client_id=${clientId}&redirect_uri=${serverURL}/direct_post_vci&response_type=id_token&response_mode=direct_post&scope=openid&nonce=${nonce}&request=${vpRequestJWT}`; - - if (errors.length > 0) { - console.error("Validation errors:", errors); - let error_description = ""; - errors.forEach((element) => { - error_description += element + " "; - }); - const encodedErrorDescription = encodeURIComponent( - error_description.trim() - ); - const errorRedirectUrl = `${redirectUri}?error=invalid_request&error_description=${encodedErrorDescription}`; - //TODO mark the codeFlowSession as failed - return res.redirect(302, errorRedirectUrl); - } else { - return res.redirect(302, redirectUrl); - } -}); -codeFlowRouter.post("/direct_post_vci", async (req, res) => { - console.log("direct_post VP for VCI is below!"); - let state = req.body["state"]; - let jwt = req.body["id_token"]; - if (jwt) { - const codeSessions = getAuthCodeSessions(); - const authorizationCode = generateNonce(16); - updateIssuerStateWithAuthCode( - authorizationCode, - state, - codeSessions.walletSessions, - codeSessions.results, - codeSessions.requests - ); - const redirectUrl = `http://localhost:8080?code=${authorizationCode}&state=${state}`; - return res.redirect(302, redirectUrl); - } else { - return res.sendStatus(500); - } -}); -function updateIssuerStateWithAuthCode( +export function updateIssuerStateWithAuthCode( code, walletState, walletSessions, @@ -216,4 +77,31 @@ function updateIssuerStateWithAuthCode( } } +export function updateIssuerStateWithAuthCodeAfterVP( + code, + issuerState, + issuerSessions, + codeFlowRequestsResults, + codeFlowRequests +) { + let index = issuerSessions.indexOf(issuerState); + if (index >= 0) { + // console.log("codeFlowRequestResults of " + index) + // console.log(codeFlowRequestsResults[index]) + // console.log(codeFlowRequests[index]) + + codeFlowRequestsResults[index]["sessionId"] = code; + codeFlowRequests[index]["sessionId"] = code; + + // console.log("UPDATED codeFlowRequestResults of " + index) + // console.log(codeFlowRequestsResults[index]) + // console.log(codeFlowRequests[index]) + + } else { + console.log("could not find session "+issuerSessions +"issuer state will not be updated with code "+code); + } +} + + + export default codeFlowRouter; diff --git a/routes/codeFlowSdJwtRoutes.js b/routes/codeFlowSdJwtRoutes.js index 6c3b7e7..eef92f7 100644 --- a/routes/codeFlowSdJwtRoutes.js +++ b/routes/codeFlowSdJwtRoutes.js @@ -1,27 +1,54 @@ import express from "express"; import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; import { v4 as uuidv4 } from "uuid"; -import { getAuthCodeSessions } from "../services/cacheService.js"; +import { + getAuthCodeSessions, + getPushedAuthorizationRequests, + getSessionsAuthorizationDetail, + getAuthCodeAuthorizationDetail, +} from "../services/cacheService.js"; +import { buildVPbyValue } from "../utils/tokenUtils.js"; import qr from "qr-image"; import imageDataURI from "image-data-uri"; import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; +import { generateNonce, buildVpRequestJWT } from "../utils/cryptoUtils.js"; +import { + updateIssuerStateWithAuthCode, + updateIssuerStateWithAuthCodeAfterVP, +} from "./codeFlowJwtRoutes.js"; const codeFlowRouterSDJWT = express.Router(); const serverURL = process.env.SERVER_URL || "http://localhost:3000"; +const proxyPath = process.env.PROXY_PATH || null; +const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); - - -// auth code flow +// ****************************************************************** +// ************* CREDENTIAL OFFER ENDPOINTS ************************* +// ****************************************************************** codeFlowRouterSDJWT.get(["/offer-code-sd-jwt"], async (req, res) => { const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); const codeSessions = getAuthCodeSessions(); + const credentialType = req.query.credentialType + ? req.query.credentialType + : "VerifiablePortableDocumentA2SDJWT"; + + const client_id_scheme = req.query.client_id_scheme + ? req.query.client_id_scheme + : "redirect_uri"; + if (codeSessions.sessions.indexOf(uuid) < 0) { codeSessions.sessions.push(uuid); // codeSessions.results.push({ sessionId: uuid, status: "pending" }); } - let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-code-sd-jwt/${uuid}`; + + let encodedCredentialOfferUri = encodeURIComponent( + `${serverURL}/credential-offer-code-sd-jwt/${uuid}?scheme=${client_id_scheme}&credentialType=${credentialType}` + ); + let credentialOffer = `openid-credential-offer://?credential_offer_uri=${encodedCredentialOfferUri}`; let code = qr.image(credentialOffer, { type: "png", @@ -39,16 +66,467 @@ codeFlowRouterSDJWT.get(["/offer-code-sd-jwt"], async (req, res) => { }); // auth code-flow request + +// with dynamic cred request and client_id_scheme == redirect_uri codeFlowRouterSDJWT.get(["/credential-offer-code-sd-jwt/:id"], (req, res) => { + const credentialType = req.query.credentialType + ? req.query.credentialType + : "VerifiablePortableDocumentA2SDJWT"; + + console.log(req.query); + console.log(req.query.client_id_scheme); + const client_id_scheme = req.query.scheme ? req.query.scheme : "redirect_uri"; + + /* + To support multiple client_id_schemas, this param client_id_scheme + will be passed into the session of the issuer and fetched in the + authorize endpoint to decide what schema to use + */ + const issuer_state = `${req.params.id}|${client_id_scheme}`; // using "|" as a delimiter + res.json({ credential_issuer: serverURL, - credentials: ["VerifiablePortableDocumentA1"], + credential_configuration_ids: [credentialType], grants: { authorization_code: { - issuer_state: req.params.id, + issuer_state: issuer_state, }, }, }); }); +/*************************************************************** + * Push Authoriozation Request Endpoints + * https://datatracker.ietf.org/doc/html/rfc9126 + ***************************************************************/ +codeFlowRouterSDJWT.post("/par", async (req, res) => { + const client_id = req.body.client_id; //DID of the holder requesting the credential + const scope = req.body.scope; + const response_type = req.body.response_type; + const redirect_uri = req.body.redirect_uri; + const code_challenge = req.body.code_challenge; + const code_challenge_method = req.body.code_challenge_method; + const claims = req.body.claims; + const state = req.body.state; + const authorizationHeader = req.get("Authorization"); + const responseType = req.body.response_type; + const issuerState = decodeURIComponent(req.body.issuer_state); // This can be associated with the ITB session + let authorizationDetails = req.body.authorization_details; + const clientMetadata = req.body.client_metadata; + + let requestURI = "urn:aegean.gr:" + uuidv4(); + let parRequests = getPushedAuthorizationRequests(); + parRequests.set(requestURI, { + client_id: client_id, + scope: scope, + response_type: response_type, + redirect_uri: redirect_uri, + code_challenge: code_challenge, + code_challenge_method: code_challenge_method, + claims: claims, + state: state, + authorizationHeader: authorizationHeader, + responseType: responseType, + issuerState: issuerState, + authorizationDetails: authorizationDetails, + clientMetadata: clientMetadata, + }); + + res.json({ + request_uri: requestURI, + expires_in: 90, + }); +}); + +/********************************************************************************* + * Authorisation request + * + * Two ways to request authorization + * One way is to use the authorization_details request parameter with one or more authorization details objects of type openid_credential + * Second way is through the use of scope + ****************************************************************/ +codeFlowRouterSDJWT.get("/authorize", async (req, res) => { + let response_type = req.query.response_type; + let scope = req.query.scope; + let issuerState = decodeURIComponent(req.query.issuer_state); // This can be associated with the ITB session + let state = req.query.state; + let client_id = decodeURIComponent(req.query.client_id); //DID of the holder requesting the credential + let authorizationDetails = req.query.authorization_details; + let redirect_uri = req.query.redirect_uri; + let code_challenge = req.query.code_challenge; + let code_challenge_method = req.query.code_challenge_method; + let authorizationHeader = req.headers["authorization"]; // Fetch the 'Authorization' header + let claims = ""; + let client_metadata = req.query.client_metadata; + + //validations + let errors = []; + + //check for par + const request_uri = req.query.request_uri; + if (request_uri) { + const parRequest = getPushedAuthorizationRequests() + ? getPushedAuthorizationRequests().get(request_uri) + : null; + if (parRequest) { + client_id = parRequest.client_id; + scope = parRequest.scope; + response_type = parRequest.response_type; + redirect_uri = parRequest.redirect_uri; + code_challenge = parRequest.code_challenge; + code_challenge_method = parRequest.code_challenge_method; + claims = parRequest.claims; + state = parRequest.state; + authorizationHeader = parRequest.authorizationHeader; + issuerState = parRequest.issuerState; + authorizationDetails = parRequest.authorizationDetails; + response_type = parRequest.response_type; + client_metadata = parRequest.clientMetadata; + } else { + console.log( + "ERROR: request_uri present in authorization endpoint, but no par request cached for request_uri" + + request_uri + ); + } + } + + const redirectUri = redirect_uri + ? decodeURIComponent(redirect_uri) + : "openid4vp://"; + const nonce = req.query.nonce; + const codeChallenge = decodeURIComponent(req.query.code_challenge); + const codeChallengeMethod = req.query.code_challenge_method; //this should equal to S256 + try { + if (client_metadata) { + const clientMetadata = JSON.parse(decodeURIComponent(client_metadata)); + } else { + console.log("client_metadata was missing"); + } + } catch (error) { + console.log("client_metadata was missing"); + console.log(error); + } + + // ************CASE 1: With authorizationDetails + /* required parameters: response_type, client_id, code_challenge, + optional: code_challenge_method, authorization_details, redirect_uri, issuer_state + */ + if (authorizationDetails) { + if (authorizationDetails && !response_type) + errors.push("authorizationDetails missing response_type"); + if (authorizationDetails && !client_id) + errors.push("authorizationDetails missing client_id"); + if (authorizationDetails && !code_challenge) + errors.push("authorizationDetails missing code_challenge"); + + try { + if (authorizationDetails) { + authorizationDetails = JSON.parse( + decodeURIComponent(authorizationDetails) + ); + } else { + console.log("authorization_details was missing"); + } + if (authorizationDetails.length > 0) { + authorizationDetails.forEach((item) => { + let cred = fetchVCTorCredentialConfigId(item); + // cache authorizationDetails for the direct_post endpoint (it is needed to assosiate it with the auth. code generated there) + getSessionsAuthorizationDetail().set( + issuerState, + decodeURIComponent(authorizationDetails) + ); + console.log("requested credentials: " + cred); + }); + } + } catch (error) { + console.log("error parsing authorization details" + authorizationDetails); + errors.push("error parsing authorization details"); + } + } + + // ************CASE 2: With Scope instead of authorization_details + + /* + [{"format":"jwt_vc", + "locations":["https://issuer.example.com"], + "type":"openid_credential", + "types":["VerifiableCredential","VerifiableAttestation","VerifiablePortableDocumentA1"]}] + */ + if (!authorizationDetails) { + console.log("authorization_details not found trying scope"); + if (scope) { + console.log("requested credentials: " + scope); + } else { + errors.push("no credentials requested"); + console.log(`no credentials requested`); + } + } + + if (response_type !== "code") { + errors.push("Invalid response_type"); + } + + // *************************************************************************** + // retrive cleaned ITB session and also get (if specified) the client_id_scheme + // *************************************************************************** + + const [originalUuid, client_id_scheme] = issuerState.split("|"); + issuerState = originalUuid; + + // ITB Sessions + const authorizationCode = null; //"SplxlOBeZQQYbYS6WxSbIA"; + const codeSessions = getAuthCodeSessions(); + if (codeSessions.sessions.indexOf(issuerState) >= 0) { + codeSessions.requests.push({ + redirectUri: redirectUri, + challenge: codeChallenge, + method: codeChallengeMethod, + sessionId: authorizationCode, + issuerState: issuerState, + state: state, + }); + codeSessions.results.push({ + sessionId: authorizationCode, + issuerState: issuerState, + state: state, + status: "pending", + }); + codeSessions.walletSessions.push(state); // push state as send by wallet + } else { + console.log("ITB session not found"); + } + + /* + Authorizatiton Endpoint Response: + */ + + //The holder wallet then responds with an id_token signed by the DID to the direct post endpoint. + + if (errors.length > 0) { + console.error("Validation errors:", errors); + let error_description = ""; + errors.forEach((element) => { + error_description += element + " "; + }); + const encodedErrorDescription = encodeURIComponent( + error_description.trim() + ); + const errorRedirectUrl = `${redirectUri}?error=invalid_request&error_description=${encodedErrorDescription}`; + //TODO mark the codeFlowSession as failed + return res.redirect(302, errorRedirectUrl); + } else { + /* + //5.1.5. Dynamic Credential Request https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#name-successful-authorization-requesttt + The credential issuer can optionally request additional details to authenticate the client e.g. DID authentication. In this case, the authorisation response will contain a response_mode parameter with the value direct_post. A sample response is as given: + */ + + if (client_id_scheme == "redirect_uri") { + console.log("client_id_scheme redirect_uri"); + const vpRedirectURI = "openid4vp://"; + // console.log( + // redirect_uri + " but i will request the vp based on " + vpRedirectURI + // ); + // changed this to support auth request by value not reference + //const redirectUrl = `${vpRedirectURI}?state=${state}&client_id=${client_id}&redirect_uri=${serverURL}/direct_post_vci&response_type=id_token&response_mode=direct_post&scope=openid&nonce=${nonce}&request_uri=${serverURL}/request_uri_dynamic`; + const response_uri = serverURL + "/direct_post_vci" + "/" + issuerState; + const presentation_definition_uri = + serverURL + "/presentation-definition/itbsdjwt"; + const client_metadata_uri = serverURL + "/client-metadata"; + + //response_uri + //const redirectUrl = buildVPbyValue(client_id,presentation_definition_uri,"redirect_uri",client_metadata_uri,response_uri) + // client_id_scheme is set to redirect_uri in OIDC4VP v20, the client_id becomes the redirect_uri + const redirectUrl = buildVPbyValue( + response_uri, + presentation_definition_uri, + "redirect_uri", + client_metadata_uri, + response_uri + ); + // console.log("redirectUrl", redirectUrl); + return res.redirect(302, redirectUrl); + } else if (client_id_scheme == "x509_san_dns") { + console.log("client_id_scheme x509_san_dns"); + let request_uri = `${serverURL}/x509VPrequest_dynamic/${issuerState}`; + const clientId = "dss.aegean.gr"; + let vpRequest = + "openid4vp://?client_id=" + + encodeURIComponent(clientId) + + "&request_uri=" + + encodeURIComponent(request_uri); + + return res.redirect(302, vpRequest); + } else if (client_id_scheme == "did:jwk") { + console.log("client_id_scheme did:jwk"); + let request_uri = `${serverURL}/didJwksVPrequest_dynamic/${issuerState}`; + const clientId = "dss.aegean.gr"; + let vpRequest = + "openid4vp://?client_id=" + + encodeURIComponent(clientId) + + "&request_uri=" + + encodeURIComponent(request_uri); + + return res.redirect(302, vpRequest); + } + } +}); + +// Dynamic VP request by reference endpoint +codeFlowRouterSDJWT.get("/x509VPrequest_dynamic/:id", async (req, res) => { + const uuid = req.params.id ? req.params.id : uuidv4(); + const response_uri = serverURL + "/direct_post_vci/" + uuid; + + const client_metadata = { + client_name: "UAegean EWC Verifier", + logo_uri: "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + location: "Greece", + cover_uri: "string", + description: "EWC pilot case verification", + vp_formats: { + "vc+sd-jwt": { + alg: ["ES256", "ES384"], + }, + }, + }; + const presentation_definition_sdJwt = JSON.parse( + fs.readFileSync("./data/presentation_definition_sdjwt.json", "utf-8") + ); + + const clientId = "dss.aegean.gr"; + let signedVPJWT = await buildVpRequestJWT( + clientId, + response_uri, + presentation_definition_sdJwt, + "", + "x509_san_dns", + client_metadata, + null, + serverURL + ); + + console.log(signedVPJWT); + res.type("text/plain").send(signedVPJWT); +}); + +codeFlowRouterSDJWT.get("/didJwksVPrequest_dynamic/:id", async (req, res) => { + const uuid = req.params.id ? req.params.id : uuidv4(); + const response_uri = serverURL + "/direct_post_vci/" + uuid; + + const client_metadata = { + client_name: "UAegean EWC Verifier", + logo_uri: "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + location: "Greece", + cover_uri: "string", + description: "EWC pilot case verification", + vp_formats: { + "vc+sd-jwt": { + alg: ["ES256", "ES384"], + }, + }, + }; + + const privateKeyPem = fs.readFileSync( + "./didjwks/did_private_pkcs8.key", + "utf8" + ); + + let contorller = serverURL; + if (proxyPath) { + contorller = serverURL.replace("/" + proxyPath, "") + ":" + proxyPath; + } + contorller = contorller.replace("https://", ""); + const clientId = `did:web:${contorller}`; + const presentation_definition_sdJwt = JSON.parse( + fs.readFileSync("./data/presentation_definition_sdjwt.json", "utf-8") + ); + + let signedVPJWT = await buildVpRequestJWT( + clientId, + response_uri, + presentation_definition_sdJwt, + privateKeyPem, + "did:jwks", + client_metadata, + `did:web:${contorller}#keys-1`, + serverURL + ); + res.type("text/plain").send(signedVPJWT); +}); + +/* + presentation by the wallet during an Issuance part of the Dynamic Credential Request +*/ +codeFlowRouterSDJWT.post("/direct_post_vci/:id", async (req, res) => { + console.log("direct_post VP for VCI is below!"); + let state = req.body["state"]; + let jwt = req.body["vp_token"]; + console.log("direct_post_vci received jwt is::"); + console.log(jwt); + const uuid = req.params.id; + + // + const authorizatiton_details = getSessionsAuthorizationDetail().get(state); + + if (jwt) { + const codeSessions = getAuthCodeSessions(); + const authorizationCode = generateNonce(16); + //cache authorizatiton_detatils witth the generated code. this is needed for the token_endpoint + getAuthCodeAuthorizationDetail().set( + authorizationCode, + authorizatiton_details + ); + + // // THE WALLET SENDS A DIFFERENT SATE (THAT IS THE STATE OF THE VP NOT THE VCI) + // // SO A DIFFERENT UPDATE IS REQUIRED HERE + // updateIssuerStateWithAuthCodeAfterVP( + // authorizationCode, + // uuid, + // codeSessions.sessions, + // codeSessions.results, + // codeSessions.requests + // ); + + let sessionIndex = codeSessions.sessions.indexOf(uuid); + + if (sessionIndex >= 0) { + let issuanceState = codeSessions.results[sessionIndex].state; + + // updateIssuerStateWithAuthCode( + // authorizationCode, + // uuid, + // codeSessions.results, + // codeSessions.requests + // ); + updateIssuerStateWithAuthCodeAfterVP( + authorizationCode, + uuid, + codeSessions.sessions, + codeSessions.results, + codeSessions.requests + ); + + const redirectUrl = `${codeSessions.requests[sessionIndex].redirectUri}?code=${authorizationCode}&state=${issuanceState}`; + // return //res.redirect(302, redirectUrl); + return res.send({ redirect_uri: redirectUrl }); + } else { + console.log("issuance session not found " + uuid); + return res.sendStatus(500); + } + } else { + return res.sendStatus(500); + } +}); + +// Function to fetch either vct or credential_configuration_id +function fetchVCTorCredentialConfigId(data) { + // Check for vct first, fallback to credential_configuration_id if not found + if (data.vct) { + return data.vct; + } else if (data.credential_configuration_id) { + return data.credential_configuration_id; + } else { + return null; // Return null if neither is found + } +} + export default codeFlowRouterSDJWT; diff --git a/routes/didweb.js b/routes/didweb.js new file mode 100644 index 0000000..4148b67 --- /dev/null +++ b/routes/didweb.js @@ -0,0 +1,60 @@ +import express from "express"; +import fs from "fs"; +import { v4 as uuidv4 } from "uuid"; +import { convertPemToJwk } from "../utils/didjwks.js"; + +const didWebRouter = express.Router(); +const serverURL = process.env.SERVER_URL || "http://localhost:3000"; +const proxyPath = process.env.PROXY_PATH || null; + +didWebRouter.get(["/.well-known/did.json","/did.json"], async (req, res) => { + let jwks = await convertPemToJwk(); + // console.log(jwks); + let contorller = serverURL; + let serviceURL = serverURL + if (proxyPath) { + contorller = serverURL.replace("/"+proxyPath,"") + ":" + proxyPath; + serviceURL = serverURL //+ "/" + proxyPath; + } + + contorller = contorller.replace("https://","") + let didDoc = { + "@context": "https://www.w3.org/ns/did/v1", + id: `did:web:${contorller}`, + verificationMethod: [ + { + id: `did:web:${contorller}#keys-1`, //"did:web:example.com#keys-1", + type: "JsonWebKey2020", + controller: `${contorller}`, + publicKeyJwk: jwks, + }, + ], + authentication: [`${contorller}#keys-1`], + + service: [ + { + id: `did:web:${contorller}#jwks`, + type: "JsonWebKey2020", + serviceEndpoint: `${serviceURL}/.well-known/jwks.json`, + }, + ], + }; + + res.json(didDoc); +}); + +didWebRouter.get(["/.well-known/jwks.json"], async (req, res) => { + let contorller = serverURL; + if (proxyPath) { + contorller = serverURL.replace("/"+proxyPath,"") + ":" + proxyPath; + } + contorller = contorller.replace("https://","") + let jwks = await convertPemToJwk(); + let result = { + keys: [{ ...jwks, use: "sig", kid: `${contorller}#keys-1` }], + }; + + res.json(result); +}); + +export default didWebRouter; diff --git a/routes/jwtVcRoutes.js b/routes/jwtVcRoutes.js new file mode 100644 index 0000000..c6d65aa --- /dev/null +++ b/routes/jwtVcRoutes.js @@ -0,0 +1,90 @@ +import express from "express"; +import fs from "fs"; +import { v4 as uuidv4 } from "uuid"; +import { + pemToJWK, + generateNonce, + base64UrlEncodeSha256, +} from "../utils/cryptoUtils.js"; +import { + buildAccessToken, + generateRefreshToken, + buildIdToken, +} from "../utils/tokenUtils.js"; + +import { + getAuthCodeSessions, + getPreCodeSessions, +} from "../services/cacheService.js"; + +import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"; +import { + createSignerVerifier, + digest, + generateSalt, +} from "../utils/sdjwtUtils.js"; +import jwt from "jsonwebtoken"; + +import qr from "qr-image"; +import imageDataURI from "image-data-uri"; +import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; +import { request } from "http"; + +const router = express.Router(); + +const serverURL = process.env.SERVER_URL || "http://localhost:3000"; + +const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); +const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); + + + + + +// *************** +///pre-auth flow jwt_vc_json +router.get(["/pre-offer-jwt"], async (req, res) => { + const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); + const preSessions = getPreCodeSessions(); + if (preSessions.sessions.indexOf(uuid) < 0) { + preSessions.sessions.push(uuid); + preSessions.results.push({ sessionId: uuid, status: "pending" }); + } + let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-pre-jwt/${uuid}`; //OfferUUID + let code = qr.image(credentialOffer, { + type: "png", + ec_level: "H", + size: 10, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: credentialOffer, + sessionId: uuid, + }); +}); + + + +function getPersonaPart(inputString) { + const personaKey = "persona="; + const personaIndex = inputString.indexOf(personaKey); + + if (personaIndex === -1) { + return null; // "persona=" not found in the string + } + + // Split the string based on "persona=" + const parts = inputString.split(personaKey); + + // Return the part after "persona=" + return parts[1] || null; +} + + + + + +export default router; diff --git a/routes/metadataroutes.js b/routes/metadataroutes.js index 1070f47..eb71e74 100644 --- a/routes/metadataroutes.js +++ b/routes/metadataroutes.js @@ -17,11 +17,16 @@ const oauthConfig = JSON.parse( const jwks = pemToJWK(publicKeyPem, "public"); + +/** + * Credential Issuer metadata + */ + metadataRouter.get( "/.well-known/openid-credential-issuer", async (req, res) => { issuerConfig.credential_issuer = serverURL; - issuerConfig.authorization_server = serverURL; + issuerConfig.authorization_servers = [serverURL]; issuerConfig.credential_endpoint = serverURL + "/credential"; issuerConfig.deferred_credential_endpoint = serverURL + "/credential_deferred"; @@ -30,6 +35,9 @@ metadataRouter.get( } ); +/** + * Authorization Server Metadata + */ metadataRouter.get( [ "/.well-known/oauth-authorization-server", @@ -39,12 +47,16 @@ metadataRouter.get( async (req, res) => { oauthConfig.issuer = serverURL; oauthConfig.authorization_endpoint = serverURL + "/authorize"; + oauthConfig.pushed_authorization_request_endpoint = serverURL + "/par"; oauthConfig.token_endpoint = serverURL + "/token_endpoint"; oauthConfig.jwks_uri = serverURL + "/jwks"; res.type("application/json").send(oauthConfig); } ); + + + metadataRouter.get(["/", "/jwks"], (req, res) => { res.json({ keys: [ diff --git a/routes/preAuthSDjwRoutes.js b/routes/preAuthSDjwRoutes.js new file mode 100644 index 0000000..ff2a497 --- /dev/null +++ b/routes/preAuthSDjwRoutes.js @@ -0,0 +1,556 @@ +import express from "express"; +import fs from "fs"; +import { v4 as uuidv4 } from "uuid"; +import { + pemToJWK, + generateNonce, + base64UrlEncodeSha256, +} from "../utils/cryptoUtils.js"; +import { + buildAccessToken, + generateRefreshToken, + buildIdToken, +} from "../utils/tokenUtils.js"; + +import { + getCredentialSubjectForPersona, + getPersonaFromAccessToken, +} from "../utils/personasUtils.js"; + +import { + getAuthCodeSessions, + getPreCodeSessions, + getAuthCodeAuthorizationDetail, +} from "../services/cacheService.js"; + +import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"; +import { + createSignerVerifier, + digest, + generateSalt, +} from "../utils/sdjwtUtils.js"; +import jwt from "jsonwebtoken"; + +import qr from "qr-image"; +import imageDataURI from "image-data-uri"; +import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; +import { request } from "http"; +import { + createPIDPayload, + createStudentIDPayload, + createAllianceIDPayload, + createFerryBoardingPassPayload, + getPIDSDJWTData, + getStudentIDSDJWTData, + getAllianceIDSDJWTData, + getFerryBoardingPassSDJWTData, + getGenericSDJWTData, + getEPassportSDJWTData, + createEPassportPayload, + getVReceiptSDJWTData +} from "../utils/credPayloadUtil.js"; + +const router = express.Router(); + +const serverURL = process.env.SERVER_URL || "http://localhost:3000"; + +const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); +const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); + +console.log("privateKey"); +console.log(privateKey); + +// ****************************************************************** +// ************* CREDENTIAL OFFER ENDPOINTS ************************* +// ****************************************************************** + +///pre-auth flow sd-jwt +/* + Generates a VCI request with pre-authorised flow with a transaction code +*/ +router.get(["/offer-tx-code"], async (req, res) => { + const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); + const credentialType = req.query.credentialType + ? req.query.credentialType + : "VerifiablePortableDocumentA2SDJWT"; + + const preSessions = getPreCodeSessions(); + if (preSessions.sessions.indexOf(uuid) < 0) { + preSessions.sessions.push(uuid); + preSessions.results.push({ sessionId: uuid, status: "pending" }); + } + let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-tx-code/${uuid}?type=${credentialType}`; //OfferUUID + let code = qr.image(credentialOffer, { + type: "png", + ec_level: "H", + size: 10, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: credentialOffer, + sessionId: uuid, + }); +}); + +/** + * pre-authorised flow with a transaction code, credential offer + */ +router.get(["/credential-offer-tx-code/:id"], (req, res) => { + const credentialType = req.query.type + ? req.query.type + : "VerifiablePortableDocumentA2SDJWT"; + console.log(credentialType); + res.json({ + credential_issuer: serverURL, + credential_configuration_ids: [credentialType], + grants: { + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": req.params.id, + tx_code: { + length: 4, + input_mode: "numeric", + description: + "Please provide the one-time code that was sent via e-mail or offline", + }, + }, + }, + }); +}); + +/** + * pre-authorised flow without a transaction code request + */ +router.get(["/offer-no-code"], async (req, res) => { + const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); + const credentialType = req.query.credentialType + ? req.query.credentialType + : "VerifiablePortableDocumentA2SDJWT"; + + const preSessions = getPreCodeSessions(); + if (preSessions.sessions.indexOf(uuid) < 0) { + preSessions.sessions.push(uuid); + preSessions.results.push({ sessionId: uuid, status: "pending" }); + } + let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-no-code/${uuid}?type=${credentialType}`; //OfferUUID + let code = qr.image(credentialOffer, { + type: "png", + ec_level: "H", + size: 10, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: credentialOffer, + sessionId: uuid, + }); +}); + +/** + * pre-authorised flow no transaction code request endpoint + */ +router.get(["/credential-offer-no-code/:id"], (req, res) => { + const credentialType = req.query.type + ? req.query.type + : "VerifiablePortableDocumentA2SDJWT"; + res.json({ + credential_issuer: serverURL, + credential_configuration_ids: [credentialType], + grants: { + "urn:ietf:params:oauth:grant-type:pre-authorized_code": { + "pre-authorized_code": req.params.id, + }, + }, + }); +}); + +// ********************************************************************* + +// ***************************************************************** +// ************* TOKEN ENDPOINTS *********************************** +// ***************************************************************** + +router.post("/token_endpoint", async (req, res) => { + // Fetch the Authorization header + const authorizationHeader = req.headers["authorization"]; // Fetch the 'Authorization' header + console.log("token_endpoint authorizatiotn header-" + authorizationHeader); + + //pre-auth code flow + const preAuthorizedCode = req.body["pre-authorized_code"]; // req.body["pre-authorized_code"] + const tx_code = req.body["tx_code"]; + + //code flow + const grantType = req.body.grant_type; + const client_id = req.body.client_id; + const code = req.body["code"]; //TODO check the code ... + const code_verifier = req.body["code_verifier"]; + const redirect_uri = req.body["redirect_uri"]; + + let generatedAccessToken = buildAccessToken(serverURL, privateKey); + + //TODO CHECK IF THE AUTHORIZATION REQUEST WAS done via a authorization_details or scope parameter + let authorization_details = getAuthCodeAuthorizationDetail().get(code); + + if (grantType == "urn:ietf:params:oauth:grant-type:pre-authorized_code") { + console.log("pre-auth code flow"); + const preSessions = getPreCodeSessions(); + let index = preSessions.sessions.indexOf(preAuthorizedCode); + if (index >= 0) { + console.log( + `credential for session ${preAuthorizedCode} has been issued` + ); + preSessions.results[index].status = "success"; + preSessions.accessTokens[index] = generatedAccessToken; + let personaId = getPersonaPart(preAuthorizedCode); + if (personaId) { + preSessions.personas[index] = personaId; + } else { + preSessions.personas[index] = null; + } + // console.log("pre-auth code flow" + preSessions.results[index].status); + } + } else { + if (grantType == "authorization_code") { + const codeSessions = getAuthCodeSessions(); + console.log("codeSessions ==> grantType == authorization_code") + console.log(codeSessions) + console.log(code) + const index = codeSessions.results.findIndex( + (result) => result.sessionId === code + ); + console.log(index) + if (index >= 0) { + codeSessions.results[index]["status"] = "success"; + } + console.log("Updatetd Code sessions") + console.log(codeSessions) + + //TODO if PKCE validattiton fails the flow should + validatePKCE( + codeSessions.requests, + code, + code_verifier, + codeSessions.results + ); + } + } + //TODO return error if code flow validation fails and is not a pre-auth flow + + if (authorization_details) { + /*{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ", + "token_type": "bearer", + "expires_in": 86400, + "c_nonce": "tZignsnFbp", + "c_nonce_expires_in": 86400, + "authorization_details": [ + { + "type": "openid_credential", + "credential_configuration_id": "VerifiablePortableDocumentA1", + "credential_identifiers": [ "VerifiablePortableDocumentA1-Spain", "VerifiablePortableDocumentA1-Sweden", "VerifiablePortableDocumentA1-Germany" ] + }]}*/ + res.json({ + access_token: generatedAccessToken, + refresh_token: generateRefreshToken(), + token_type: "bearer", + expires_in: 86400, + // id_token: buildIdToken(serverURL, privateKey), + c_nonce: generateNonce(), + c_nonce_expires_in: 86400, + authorization_details: authorizatiton_details, + }); + } else { + res.json({ + /* "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ", + "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI4a5k..zEF", + "token_type": "bearer", + "expires_in": 86400, + "id_token": "eyJodHRwOi8vbWF0dHIvdGVuYW50L..3Mz", + "c_nonce": "PAPPf3h9lexTv3WYHZx8ajTe", + "c_nonce_expires_in": 86400 */ + + access_token: generatedAccessToken, + refresh_token: generateRefreshToken(), + token_type: "bearer", + expires_in: 86400, + id_token: buildIdToken(serverURL, privateKey), + c_nonce: generateNonce(), + c_nonce_expires_in: 86400, + }); + } +}); + +// ***************************************************************** +// ************* CREDENTIAL ENDPOINTS ****************************** +// ***************************************************************** + +router.post("/credential", async (req, res) => { + const authHeader = req.headers["authorization"]; + const token = authHeader && authHeader.split(" ")[1]; // Split "Bearer" and the token + const requestBody = req.body; + const format = requestBody.format; + const requestedCredentials = requestBody.credential_definition + ? requestBody.credential_definition.type + : null; + const decodedHeaderSubjectDID = + requestBody.proof && requestBody.proof.jwt + ? jwt.decode(requestBody.proof.jwt, { complete: true }).payload.iss + : null; + + if (!requestBody.proof || !requestBody.proof.jwt) { + console.log("NO keybinding info found!!!"); + return res.status(400).json({ error: "No proof information found" }); + } + + let payload = {}; + + if (format === "jwt_vc_json") { + console.log("jwt ", requestedCredentials); + if (requestedCredentials && requestedCredentials[0] === "PID") { + payload = createPIDPayload(token, serverURL, decodedHeaderSubjectDID); + } else if ( + requestedCredentials && + requestedCredentials[0] === "ePassportCredential" + ) { + payload = createEPassportPayload(serverURL, decodedHeaderSubjectDID); + } else if ( + requestedCredentials && + requestedCredentials[0] === "StudentID" + ) { + payload = createStudentIDPayload(serverURL, decodedHeaderSubjectDID); + } else if ( + requestedCredentials && + requestedCredentials[0] === "ferryBoardingPassCredential" + ) { + payload = createEPassportPayload(serverURL, decodedHeaderSubjectDID); + } + + const signOptions = { algorithm: "ES256" }; + const additionalHeaders = { kid: "aegean#authentication-key", typ: "JWT" }; + const idtoken = jwt.sign(payload, privateKey, { + ...signOptions, + header: additionalHeaders, + }); + + res.json({ + format: "jwt_vc_json", + credential: idtoken, + c_nonce: generateNonce(), + c_nonce_expires_in: 86400, + }); + } else if (format === "vc+sd-jwt") { + let vct = requestBody.vct; + console.log("vc+sd-jwt ", vct); + + let decodedHeaderSubjectDID; + if (requestBody.proof && requestBody.proof.jwt) { + // console.log(requestBody.proof.jwt) + let decodedWithHeader = jwt.decode(requestBody.proof.jwt, { + complete: true, + }); + let holderJWKS = decodedWithHeader.header; + // console.log("Token:", token); + // console.log("Request Body:", requestBody); + let credType = vct; // VerifiablePortableDocumentA1SDJWT or VerifiablePortableDocumentA2SDJWT + const { signer, verifier } = await createSignerVerifier( + pemToJWK(privateKey, "private"), + pemToJWK(publicKeyPem, "public") + ); + const sdjwt = new SDJwtVcInstance({ + signer, + verifier, + signAlg: "ES256", + hasher: digest, + hashAlg: "sha-256", + saltGenerator: generateSalt, + }); + + let credPayload = {}; + try { + if (credType === "VerifiablePIDSDJWT") { + credPayload = getPIDSDJWTData(decodedHeaderSubjectDID); + } else if (credType === "VerifiableePassportCredentialSDJWT") { + credPayload = getEPassportSDJWTData(decodedHeaderSubjectDID); + } else if (credType === "VerifiableStudentIDSDJWT") { + credPayload = getStudentIDSDJWTData(decodedHeaderSubjectDID); + } else if (credType === "ferryBoardingPassCredential") { + credPayload = VerifiableFerryBoardingPassCredentialSDJWT( + decodedHeaderSubjectDID + ); + } else if (credType === "VerifiablePortableDocumentA1SDJWT") { + credPayload = getGenericSDJWTData(decodedHeaderSubjectDID); + } else if (credType === "VerifiablevReceiptSDJWT") { + credPayload = getVReceiptSDJWTData(decodedHeaderSubjectDID); + } + else if (credType === "VerifiablePortableDocumentA2SDJWT") { + credPayload = getGenericSDJWTData(decodedHeaderSubjectDID); + } + + const cnf = { jwk: holderJWKS.jwk }; + // console.log(credType); + console.log(credPayload.claims); + // console.log(credPayload.disclosureFrame); + + const credential = await sdjwt.issue( + { + iss: serverURL, + iat: Math.floor(Date.now() / 1000), + vct: credType, + ...credPayload.claims, + cnf: cnf, + }, + credPayload.disclosureFrame + ); + + console.log("sending credential"); + console.log({ + format: "vc+sd-jwt", + credential: credential, + c_nonce: generateNonce(), + c_nonce_expires_in: 86400, + }); + + res.json({ + format: "vc+sd-jwt", + credential: credential, + c_nonce: generateNonce(), + c_nonce_expires_in: 86400, + }); + } catch (error) { + console.log(error); + } + } else { + console.log( + "requestBody.proof && requestBody.proof.jwt not found", + requestBody + ); + return res.status(400).json({ error: "proof not found" }); + } + + // + } else { + console.log("UNSUPPORTED FORMAT:", format); + return res.status(400).json({ error: "Unsupported format" }); + } +}); + +//issuerConfig.credential_endpoint = serverURL + "/credential"; + +//ITB +router.get(["/issueStatus"], (req, res) => { + let sessionId = req.query.sessionId; + + // walletCodeSessions,codeFlowRequestsResults,codeFlowRequests + const preSessions = getPreCodeSessions(); + const codeSessions = getAuthCodeSessions(); + let result = + checkIfExistsIssuanceStatus( + sessionId, + preSessions.sessions, + preSessions.results + ) || + checkIfExistsIssuanceStatus( + sessionId, + codeSessions.sessions, + codeSessions.results, + codeSessions.walletSessions, + codeSessions.requests + ); + if (result) { + console.log("wi9ll send result"); + console.log({ + status: result, + reason: "ok", + sessionId: sessionId, + }); + res.json({ + status: result, + reason: "ok", + sessionId: sessionId, + }); + } else { + res.json({ + status: "failed", + reason: "not found", + sessionId: sessionId, + }); + } +}); + +function checkIfExistsIssuanceStatus( + sessionId, + sessions, + sessionResults, + walletCodeSessions = null, + codeFlowRequests = null +) { + let index = sessions.indexOf(sessionId); + console.log("index is"); + console.log(index); + // if (index < 0) { + // sessions.forEach((value, _index) => { + // console.log("checking value to " + value.replace(/-persona=\s+$/, "") +"-checking vs" + sessionId) + // if (value.replace(/-persona=.*$/, "") === sessionId) { + // console.log("updated index") + // index = _index; + // } + // }); + // } + if (index >= 0 && sessionResults[index]) { + let status = sessionResults[index].status; + console.log(`sending status ${status} for session ${sessionId}`); + console.log(`new sessions`); + console.log(sessions); + console.log("new session statuses"); + console.log(sessionResults); + if (status === "success") { + sessions.splice(index, 1); + sessionResults.splice(index, 1); + if (walletCodeSessions) walletCodeSessions.splice(index, 1); + if (codeFlowRequests) codeFlowRequests.splice(index, 1); + } + return status; + } + return null; +} + +async function validatePKCE(sessions, code, code_verifier, issuanceResults) { + for (let i = 0; i < sessions.length; i++) { + let element = sessions[i]; + if (code === element.sessionId) { + let challenge = element.challenge; + let tester = await base64UrlEncodeSha256(code_verifier); + if (tester === challenge) { + issuanceResults[i].status = "success"; + console.log("code flow status:" + issuanceResults[i].status); + } else { + console.log( + "PCKE ERROR tester and challenge do not match " + + tester + + "-" + + challenge + ); + } + } + } +} + +function getPersonaPart(inputString) { + const personaKey = "persona="; + const personaIndex = inputString.indexOf(personaKey); + + if (personaIndex === -1) { + return null; // "persona=" not found in the string + } + + // Split the string based on "persona=" + const parts = inputString.split(personaKey); + + // Return the part after "persona=" + return parts[1] || null; +} + +export default router; diff --git a/routes/routes.js b/routes/routes.js deleted file mode 100644 index 21febf6..0000000 --- a/routes/routes.js +++ /dev/null @@ -1,907 +0,0 @@ -import express from "express"; -import fs from "fs"; -import { v4 as uuidv4 } from "uuid"; -import { - pemToJWK, - generateNonce, - base64UrlEncodeSha256, -} from "../utils/cryptoUtils.js"; -import { - buildAccessToken, - generateRefreshToken, - buildIdToken, -} from "../utils/tokenUtils.js"; - -import { - getAuthCodeSessions, - getPreCodeSessions, -} from "../services/cacheService.js"; - -import { SDJwtVcInstance } from "@sd-jwt/sd-jwt-vc"; -import { - createSignerVerifier, - digest, - generateSalt, -} from "../utils/sdjwtUtils.js"; -import jwt from "jsonwebtoken"; - -import qr from "qr-image"; -import imageDataURI from "image-data-uri"; -import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; -import { request } from "http"; - -const router = express.Router(); - -const serverURL = process.env.SERVER_URL || "http://localhost:3000"; - -const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); -const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); - -console.log("privateKey"); -console.log(privateKey); - -///pre-auth flow sd-jwt -router.get(["/offer"], async (req, res) => { - const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); - const preSessions = getPreCodeSessions(); - if (preSessions.sessions.indexOf(uuid) < 0) { - preSessions.sessions.push(uuid); - preSessions.results.push({ sessionId: uuid, status: "pending" }); - } - - // console.log("active sessions"); - // console.log(issuanceResults); - let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer/${uuid}`; //OfferUUID - - let code = qr.image(credentialOffer, { - type: "png", - ec_level: "H", - size: 10, - margin: 10, - }); - let mediaType = "PNG"; - let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); - res.json({ - qr: encodedQR, - deepLink: credentialOffer, - sessionId: uuid, - }); -}); - -//pre-auth flow request sd-jwt -router.get(["/credential-offer/:id"], (req, res) => { - res.json({ - credential_issuer: serverURL, - credentials: ["VerifiablePortableDocumentA1"], - grants: { - "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": req.params.id, - user_pin_required: true, - }, - }, - }); -}); - -// *************** -///pre-auth flow jwt_vc_json -router.get(["/pre-offer-jwt"], async (req, res) => { - const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); - const preSessions = getPreCodeSessions(); - if (preSessions.sessions.indexOf(uuid) < 0) { - preSessions.sessions.push(uuid); - preSessions.results.push({ sessionId: uuid, status: "pending" }); - } - let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-pre-jwt/${uuid}`; //OfferUUID - let code = qr.image(credentialOffer, { - type: "png", - ec_level: "H", - size: 10, - margin: 10, - }); - let mediaType = "PNG"; - let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); - res.json({ - qr: encodedQR, - deepLink: credentialOffer, - sessionId: uuid, - }); -}); - -//pre-auth flow request sd-jwt -router.get(["/credential-offer-pre-jwt/:id"], (req, res) => { - res.json({ - credential_issuer: serverURL, - credentials: ["VerifiablePortableDocumentA2"], - grants: { - "urn:ietf:params:oauth:grant-type:pre-authorized_code": { - "pre-authorized_code": req.params.id, - user_pin_required: true, - }, - }, - }); -}); - -function getPersonaPart(inputString) { - const personaKey = "persona="; - const personaIndex = inputString.indexOf(personaKey); - - if (personaIndex === -1) { - return null; // "persona=" not found in the string - } - - // Split the string based on "persona=" - const parts = inputString.split(personaKey); - - // Return the part after "persona=" - return parts[1] || null; -} - -router.post("/token_endpoint", async (req, res) => { - //pre-auth code flow - const grantType = req.body.grant_type; - const preAuthorizedCode = req.body["pre-authorized_code"]; // req.body["pre-authorized_code"] - const userPin = req.body["user_pin"]; - //code flow - const code = req.body["code"]; //TODO check the code ... - const code_verifier = req.body["code_verifier"]; - const redirect_uri = req.body["redirect_uri"]; - - // console.log("token_endpoint parameters received"); - // console.log(grantType); - // console.log(preAuthorizedCode); - // console.log(userPin); - // console.log("---------"); - - let generatedAccessToken = buildAccessToken(serverURL, privateKey); - - if (grantType == "urn:ietf:params:oauth:grant-type:pre-authorized_code") { - console.log("pre-auth code flow"); - const preSessions = getPreCodeSessions(); - let index = preSessions.sessions.indexOf(preAuthorizedCode); - if (index >= 0) { - console.log( - `credential for session ${preAuthorizedCode} has been issued` - ); - preSessions.results[index].status = "success"; - preSessions.accessTokens[index] = generatedAccessToken; - - let personaId = getPersonaPart(preAuthorizedCode); - if (personaId) { - preSessions.personas[index] = personaId; - } else { - preSessions.personas[index] = null; - } - - // console.log("pre-auth code flow" + preSessions.results[index].status); - } - } else { - if (grantType == "authorization_code") { - const codeSessions = getAuthCodeSessions(); - validatePKCE( - codeSessions.requests, - code, - code_verifier, - codeSessions.results - ); - } - } - //TODO return error if code flow validation fails and is not a pre-auth flow - res.json({ - access_token: generatedAccessToken, - refresh_token: generateRefreshToken(), - token_type: "bearer", - expires_in: 86400, - id_token: buildIdToken(serverURL, privateKey), - c_nonce: generateNonce(), - c_nonce_expires_in: 86400, - }); -}); - -router.post("/credential", async (req, res) => { - // console.log("7 ROUTE /credential CALLED!!!!!!"); - const authHeader = req.headers["authorization"]; - const token = authHeader && authHeader.split(" ")[1]; // Split "Bearer" and the token - // Accessing the body data - const requestBody = req.body; - const format = requestBody.format; - const requestedCredentials = requestBody.credential_definition - ? requestBody.credential_definition.type - : null; //removed requestBody.types to conform to RFC001 - //TODO valiate bearer header - let decodedWithHeader; - let decodedHeaderSubjectDID; - if (requestBody.proof && requestBody.proof.jwt) { - // console.log(requestBody.proof.jwt) - decodedWithHeader = jwt.decode(requestBody.proof.jwt, { complete: true }); - // console.log(decodedWithHeader.payload.iss); - decodedHeaderSubjectDID = decodedWithHeader.payload.iss; - } - - // console.log(credential); - if (format === "jwt_vc_json") { - let payload = {}; - if (requestedCredentials != null && requestedCredentials[0] === "PID") { - //get persona if existing from accessToken - const preSessions = getPreCodeSessions(); - let persona = getPersonaFromAccessToken( - token, - preSessions.personas, - preSessions.accessTokens - ); - - let credentialSubject = { - id: decodedHeaderSubjectDID, - family_name: "Doe", - given_name: "John", - birth_date: "1990-01-01", - age_over_18: true, - issuance_date: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expiry_date: new Date( - Math.floor(Date.now() + 60 / 1000) * 1000 - ).toISOString(), - issuing_authority: "https://authority.example.com", - issuing_country: "GR", - }; - if (persona === "1") { - credentialSubject = { - id: decodedHeaderSubjectDID, - family_name: "Conti", - given_name: "Mario", - birth_date: "1988-11-12", - age_over_18: true, - issuance_date: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expiry_date: new Date( - Math.floor(Date.now() + 60 / 1000) * 1000 - ).toISOString(), - issuing_authority: "https://authority.example.com", - issuing_country: "IT", - }; - } else if (persona === "2") { - credentialSubject = { - id: decodedHeaderSubjectDID, - family_name: "Matkalainen", - given_name: "Hannah", - birth_date: "2005-02-07", - age_over_18: true, - issuance_date: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expiry_date: new Date( - Math.floor(Date.now() + 60 / 1000) * 1000 - ).toISOString(), - issuing_authority: "https://authority.example.com", - issuing_country: "FI", - }; - } else if (persona === "3") { - credentialSubject = { - id: decodedHeaderSubjectDID, - family_name: "Fischer", - given_name: "Felix", - birth_date: "1953-01-23", - age_over_18: true, - issuance_date: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expiry_date: new Date( - Math.floor(Date.now() + 60 / 1000) * 1000 - ).toISOString(), - issuing_authority: "https://authority.example.com", - issuing_country: "FI", - }; - } - - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (1 hour from now) - iat: Math.floor(Date.now() / 1000), // Token issued at time - // nbf: Math.floor(Date.now() / 1000), - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - credentialSubject: credentialSubject, - expirationDate: new Date( - (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 - ).toISOString(), - id: decodedHeaderSubjectDID, - issuanceDate: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - issued: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), - issuer: serverURL, - type: ["PID"], - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://europa.eu/2018/credentials/eudi/pid/v1", - ], - issuer: serverURL, - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - }, - // Optional claims - }; - } else { - if ( - requestedCredentials != null && - requestedCredentials[0] === "ePassportCredential" - ) { - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - iat: Math.floor(Date.now() / 1000), // Token issued at time - exp: Math.floor(Date.now() / 1000) + 60 * 60, // Token expiration time (1 hour from now) - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - credentialSubject: { - id: decodedHeaderSubjectDID || "", // Replace with the actual subject DID - electronicPassport: { - dataGroup1: { - birthdate: "1990-01-01", - docTypeCode: "P", - expiryDate: "2030-01-01", - genderCode: "M", - holdersName: "John Doe", - issuerCode: "GR", - natlText: "Hellenic", - passportNumberIdentifier: "123456789", - }, - dataGroup15: { - activeAuthentication: { - publicKeyBinaryObject: "somePublicKeyUri", - }, - }, - dataGroup2EncodedFaceBiometrics: { - faceBiometricDataEncodedPicture: "someBiometricUri", - }, - digitalTravelCredential: { - contentInfo: { - versionNumber: 1, - signatureInfo: { - digestHashAlgorithmIdentifier: "SHA-256", - signatureAlgorithmIdentifier: "RS256", - signatureCertificateText: "someCertificateText", - signatureDigestResultBinaryObject: "someDigestResultUri", - signedAttributes: { - attributeTypeCode: "someTypeCode", - attributeValueText: "someValueText", - }, - }, - }, - dataCapabilitiesInfo: { - dataTransferInterfaceTypeCode: "NFC", - securityAssuranceLevelIndText: "someSecurityLevel", - userConsentInfoText: "userConsentRequired", - virtualComponentPresenceInd: true, - }, - dataContent: { - dataGroup1: { - birthdate: "1990-01-01", - docTypeCode: "P", - expiryDate: "2030-01-01", - genderCode: "M", - holdersName: "John Doe", - issuerCode: "GR", - natlText: "Hellenic", - passportNumberIdentifier: "123456789", - personalNumberIdentifier: "987654321", - }, - dataGroup2EncodedFaceBiometrics: { - faceBiometricDataEncodedPicture: "someBiometricUri", - }, - docSecurityObject: { - dataGroupHash: [ - { - dataGroupNumber: 1, - valueBinaryObject: "someHashUri", - }, - ], - digestHashAlgorithmIdentifier: "SHA-256", - versionNumber: 1, - }, - }, - docSecurityObject: { - dataGroupHash: [ - { - dataGroupNumber: 1, - valueBinaryObject: "someHashUri", - }, - ], - digestHashAlgorithmIdentifier: "SHA-256", - versionNumber: 1, - }, - }, - }, - }, - type: ["ePassportCredential"], - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://schemas.prod.digitalcredentials.iata.org/contexts/iata_credential.jsonld", - ], - issuer: serverURL, - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - }, - }; - } else { - if ( - (requestedCredentials != null && - requestedCredentials[0] === "EducationalID") || - requestedCredentials[0] === "StudentID" - ) { - const preSessions = getPreCodeSessions(); - let persona = getPersonaFromAccessToken( - token, - preSessions.personas, - preSessions.accessTokens - ); - - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - iat: Math.floor(Date.now() / 1000), // Token issued at time - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (1 hour from now) - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - type: ["StudentID"], //["EducationalID"], - "@context": ["https://www.w3.org/2018/credentials/v1"], - issuer: serverURL, - credentialSubject: { - id: decodedHeaderSubjectDID || "", - identifier: "john.doe@university.edu", - schacPersonalUniqueCode: [ - "urn:schac:personalUniqueCode:int:esi:university.edu:12345", - ], - schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", - schacHomeOrganization: "university.edu", - familyName: "Doe", - firstName: "John", - displayName: "John Doe", - dateOfBirth: "1990-01-01", - commonName: "Johnathan Doe", - mail: "john.doe@university.edu", - eduPersonPrincipalName: "john.doe@university.edu", - eduPersonPrimaryAffiliation: "student", - eduPersonAffiliation: ["member", "student"], - eduPersonScopedAffiliation: ["student@university.edu"], - eduPersonAssurance: [ - "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", - ], - }, - issuanceDate: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expirationDate: new Date( - (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 - ).toISOString(), - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - }, - }; - - if (persona === "1") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", - identifier: "mario.conti@ewc.eu", - schacPersonalUniqueCode: [ - "urn:schac:personalUniqueCode:int:esi:university.edu:12345", - ], - schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", - schacHomeOrganization: "university.edu", - familyName: "Conti", - firstName: "Mario", - displayName: "Mario Conti", - dateOfBirth: "1990-01-01", - commonName: "Mario Contri", - mail: "mario.conti@ewc.eu", - eduPersonPrincipalName: "mario.conti@ewc.eu", - eduPersonPrimaryAffiliation: "student", - eduPersonAffiliation: ["member", "student"], - eduPersonScopedAffiliation: ["student@ewc.eu"], - eduPersonAssurance: [ - "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", - ], - }; - } else if (persona === "2") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", - identifier: "hannah@ewc.eu", - schacPersonalUniqueCode: [ - "urn:schac:personalUniqueCode:int:esi:university.edu:12345", - ], - schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", - schacHomeOrganization: "university.edu", - familyName: "Matkalainen", - firstName: "Hannah", - displayName: "Hannah Matkalainen", - dateOfBirth: "1990-01-01", - commonName: "Hannah Matkalainen", - mail: "hannah@ewc.eu", - eduPersonPrincipalName: "hannah@ewc.eu", - eduPersonPrimaryAffiliation: "student", - eduPersonAffiliation: ["member", "student"], - eduPersonScopedAffiliation: ["student@ewc.eu"], - eduPersonAssurance: [ - "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", - ], - }; - } else if (persona === "3") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", - identifier: "felix@ewc.eu", - schacPersonalUniqueCode: [ - "urn:schac:personalUniqueCode:int:esi:university.edu:12345", - ], - schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", - schacHomeOrganization: "university.edu", - familyName: "Fischer", - firstName: "Felix", - displayName: "Felix Fischer", - dateOfBirth: "1990-01-01", - commonName: "Felix Fischer", - mail: "felix@ewc.eu", - eduPersonPrincipalName: "felix@ewc.eu", - eduPersonPrimaryAffiliation: "student", - eduPersonAffiliation: ["member", "student"], - eduPersonScopedAffiliation: ["student@ewc.eu"], - eduPersonAssurance: [ - "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", - ], - }; - } - } else { - if ( - requestedCredentials != null && - requestedCredentials[0] === "allianceIDCredential" - ) { - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - iat: Math.floor(Date.now() / 1000), // Token issued at time - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (1 hour from now) - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - type: ["allianceIDCredential"], - "@context": ["https://www.w3.org/2018/credentials/v1"], - issuer: serverURL, - credentialSubject: { - id: decodedHeaderSubjectDID, // Replace with the actual subject DID - identifier: { - schemeID: "European Student Identifier", - value: - "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", - id: "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", - }, - }, - issuanceDate: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expirationDate: new Date( - (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 - ).toISOString(), - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - }, - }; - } else if ( - requestedCredentials != null && - requestedCredentials[0] === "ferryBoardingPassCredential" - ) { - const preSessions = getPreCodeSessions(); - let persona = getPersonaFromAccessToken( - token, - preSessions.personas, - preSessions.accessTokens - ); - - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - iat: Math.floor(Date.now() / 1000), // Token issued at time - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (1 hour from now) - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - type: ["VerifiableCredential", "ferryBoardingPassCredential"], - "@context": ["https://www.w3.org/2018/credentials/v1"], - issuer: serverURL, - issuanceDate: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - expirationDate: new Date( - (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 - ).toISOString(), - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - credentialSubject: { - id: decodedHeaderSubjectDID || "", // Replace with the actual subject DID - identifier: "John Doe", - ticketQR: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHkAAAB5AQAAAAA+SX7VAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABlUlEQVRIDe2YMUtCQRCFZxQVhQo2hdwV2NhZ+ggLFzcBrcNLYktjb2AiBBkDEtFiFE3d7WVo+BR+BX+gtEop3Rjb7ndmxmh8HTIz5TdvzZk5MhGoUZ1Hnz1swr3/ehyHt6nuKPexG5XQPeDqcgrBXT3wGso+ULuLoDr/owxPI27WqZLPKpFvH2FqESkBu8WqwCrCL3r6Ne3Slo6I0A9H/UUXH5KoJwUbPFLU5CJqsZubAiZwVLja4YpAyyKcijulVDrwjeMaLs5CmlgHds1GZc9K8T/AXTDwLB0yzhFb2CHavBtL68YRuBN3le6HB54Rz6MPGoNx7N2e3ws+b9YL2scQY8jI9iA9gN0FbgeEQLzUXwl90kpAmNyDe4SjH5itwbPfNRi7w0Ogl8KiB1QWUDZc0h34sFjDwrIs+w6GCSnNhWbzP9FAvVd8BzCgbChAkd4VnLQZX9VaQd9gM0b9D/UZoTQAAAABJRU5ErkJggg==", - ticketNumber: "ABC123456789", - ticketLet: "A", - lastName: "Doe", - firstName: "John", - seatType: "Economy", - seatNumber: "12A", - departureDate: "2023-11-30", - departureTime: "13:07:34", - arrivalDate: "2023-11-30", - arrivalTime: "15:30:00", - arrivalPort: "NYC", - vesselDescription: "Ferry XYZ", - }, - }, - }; - - if (persona === "1") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", // Replace with the actual subject DID - identifier: "Mario Conti", - ticketQR: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHkAAAB5AQAAAAA+SX7VAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABlUlEQVRIDe2YMUtCQRCFZxQVhQo2hdwV2NhZ+ggLFzcBrcNLYktjb2AiBBkDEtFiFE3d7WVo+BR+BX+gtEop3Rjb7ndmxmh8HTIz5TdvzZk5MhGoUZ1Hnz1swr3/ehyHt6nuKPexG5XQPeDqcgrBXT3wGso+ULuLoDr/owxPI27WqZLPKpFvH2FqESkBu8WqwCrCL3r6Ne3Slo6I0A9H/UUXH5KoJwUbPFLU5CJqsZubAiZwVLja4YpAyyKcijulVDrwjeMaLs5CmlgHds1GZc9K8T/AXTDwLB0yzhFb2CHavBtL68YRuBN3le6HB54Rz6MPGoNx7N2e3ws+b9YL2scQY8jI9iA9gN0FbgeEQLzUXwl90kpAmNyDe4SjH5itwbPfNRi7w0Ogl8KiB1QWUDZc0h34sFjDwrIs+w6GCSnNhWbzP9FAvVd8BzCgbChAkd4VnLQZX9VaQd9gM0b9D/UZoTQAAAABJRU5ErkJggg==", - ticketNumber: "3022", - ticketLet: "Y", - lastName: "Conti", - firstName: "Mario", - seatType: "Economy", - seatNumber: "12A", - departureDate: "2024-08-17", - departureTime: "13:07:34", - arrivalDate: "2024-08-17", - arrivalTime: "15:30:00", - arrivalPort: "MYKONOS TEST", - vesselDescription: "MYKONOS TEST", - }; - } else if (persona === "2") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", // Replace with the actual subject DID - identifier: "Hannah Matkalainen", - ticketQR: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHkAAAB5AQAAAAA+SX7VAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABlUlEQVRIDe2YMUtCQRCFZxQVhQo2hdwV2NhZ+ggLFzcBrcNLYktjb2AiBBkDEtFiFE3d7WVo+BR+BX+gtEop3Rjb7ndmxmh8HTIz5TdvzZk5MhGoUZ1Hnz1swr3/ehyHt6nuKPexG5XQPeDqcgrBXT3wGso+ULuLoDr/owxPI27WqZLPKpFvH2FqESkBu8WqwCrCL3r6Ne3Slo6I0A9H/UUXH5KoJwUbPFLU5CJqsZubAiZwVLja4YpAyyKcijulVDrwjeMaLs5CmlgHds1GZc9K8T/AXTDwLB0yzhFb2CHavBtL68YRuBN3le6HB54Rz6MPGoNx7N2e3ws+b9YL2scQY8jI9iA9gN0FbgeEQLzUXwl90kpAmNyDe4SjH5itwbPfNRi7w0Ogl8KiB1QWUDZc0h34sFjDwrIs+w6GCSnNhWbzP9FAvVd8BzCgbChAkd4VnLQZX9VaQd9gM0b9D/UZoTQAAAABJRU5ErkJggg==", - ticketNumber: "3022", - ticketLet: "Y", - lastName: "Matkalainen", - firstName: "Hannah", - seatType: "Economy", - seatNumber: "12A", - departureDate: "2024-08-17", - departureTime: "13:07:34", - arrivalDate: "2024-08-17", - arrivalTime: "15:30:00", - arrivalPort: "MYKONOS TEST", - vesselDescription: "MYKONOS TEST", - }; - } else if (persona === "3") { - payload.vc.credentialSubject = { - id: decodedHeaderSubjectDID || "", // Replace with the actual subject DID - identifier: "Felix Fischer", - ticketQR: - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHkAAAB5AQAAAAA+SX7VAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABlUlEQVRIDe2YMUtCQRCFZxQVhQo2hdwV2NhZ+ggLFzcBrcNLYktjb2AiBBkDEtFiFE3d7WVo+BR+BX+gtEop3Rjb7ndmxmh8HTIz5TdvzZk5MhGoUZ1Hnz1swr3/ehyHt6nuKPexG5XQPeDqcgrBXT3wGso+ULuLoDr/owxPI27WqZLPKpFvH2FqESkBu8WqwCrCL3r6Ne3Slo6I0A9H/UUXH5KoJwUbPFLU5CJqsZubAiZwVLja4YpAyyKcijulVDrwjeMaLs5CmlgHds1GZc9K8T/AXTDwLB0yzhFb2CHavBtL68YRuBN3le6HB54Rz6MPGoNx7N2e3ws+b9YL2scQY8jI9iA9gN0FbgeEQLzUXwl90kpAmNyDe4SjH5itwbPfNRi7w0Ogl8KiB1QWUDZc0h34sFjDwrIs+w6GCSnNhWbzP9FAvVd8BzCgbChAkd4VnLQZX9VaQd9gM0b9D/UZoTQAAAABJRU5ErkJggg==", - ticketNumber: "3022", - ticketLet: "Y", - lastName: "Fischer", - firstName: "Felix", - seatType: "Economy", - seatNumber: "12A", - departureDate: "2024-08-17", - departureTime: "13:07:34", - arrivalDate: "2024-08-17", - arrivalTime: "15:30:00", - arrivalPort: "MYKONOS TEST", - vesselDescription: "MYKONOS TEST", - }; - } - } else { - //sign as jwt - payload = { - iss: serverURL, - sub: decodedHeaderSubjectDID || "", - exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (1 hour from now) - iat: Math.floor(Date.now() / 1000), // Token issued at time - // nbf: Math.floor(Date.now() / 1000), - jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - vc: { - credentialSubject: { - id: null, - given_name: "John", - last_name: "Doe", - }, - expirationDate: new Date( - (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 - ).toISOString(), - id: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", - issuanceDate: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - issued: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - issuer: serverURL, - type: ["VerifiablePortableDocumentA2"], - validFrom: new Date( - Math.floor(Date.now() / 1000) * 1000 - ).toISOString(), - }, - // Optional claims - }; - } - } - } - } - - const signOptions = { - algorithm: "ES256", // Specify the signing algorithm - }; - - // Define additional JWT header fields - const additionalHeaders = { - kid: "aegean#authentication-key", - typ: "JWT", - }; - // Sign the token - const idtoken = jwt.sign(payload, privateKey, { - ...signOptions, - header: additionalHeaders, // Include additional headers separately - }); - - // console.log(idtoken); - - /* jwt format */ - res.json({ - format: "jwt_vc_json", - credential: idtoken, - c_nonce: generateNonce(), - c_nonce_expires_in: 86400, - }); - } else { - // console.log("Token:", token); - // console.log("Request Body:", requestBody); - const { signer, verifier } = await createSignerVerifier( - pemToJWK(privateKey, "private"), - pemToJWK(publicKeyPem, "public") - ); - const sdjwt = new SDJwtVcInstance({ - signer, - verifier, - signAlg: "ES256", - hasher: digest, - hashAlg: "SHA-256", - saltGenerator: generateSalt, - }); - const claims = { - given_name: "John", - last_name: "Doe", - }; - const disclosureFrame = { - _sd: ["given_name", "last_name"], - }; - const credential = await sdjwt.issue( - { - iss: serverURL, - iat: new Date().getTime(), - vct: "VerifiablePortableDocumentA1", - ...claims, - }, - disclosureFrame - ); - res.json({ - format: "vc+sd-jwt", - credential: credential, - c_nonce: generateNonce(), - c_nonce_expires_in: 86400, - }); - } -}); -//issuerConfig.credential_endpoint = serverURL + "/credential"; - -//ITB -router.get(["/issueStatus"], (req, res) => { - let sessionId = req.query.sessionId; - - // walletCodeSessions,codeFlowRequestsResults,codeFlowRequests - const preSessions = getPreCodeSessions(); - const codeSessions = getAuthCodeSessions(); - let result = - checkIfExistsIssuanceStatus( - sessionId, - preSessions.sessions, - preSessions.results - ) || - checkIfExistsIssuanceStatus( - sessionId, - codeSessions.sessions, - codeSessions.results, - codeSessions.walletSessions, - codeSessions.requests - ); - if (result) { - console.log("wi9ll send result"); - console.log({ - status: result, - reason: "ok", - sessionId: sessionId, - }); - res.json({ - status: result, - reason: "ok", - sessionId: sessionId, - }); - } else { - res.json({ - status: "failed", - reason: "not found", - sessionId: sessionId, - }); - } -}); - -function checkIfExistsIssuanceStatus( - sessionId, - sessions, - sessionResults, - walletCodeSessions = null, - codeFlowRequests = null -) { - let index = sessions.indexOf(sessionId); - console.log("index is"); - console.log(index); - // if (index < 0) { - // sessions.forEach((value, _index) => { - // console.log("checking value to " + value.replace(/-persona=\s+$/, "") +"-checking vs" + sessionId) - // if (value.replace(/-persona=.*$/, "") === sessionId) { - // console.log("updated index") - // index = _index; - // } - // }); - // } - if (index >= 0) { - let status = sessionResults[index].status; - console.log(`sending status ${status} for session ${sessionId}`); - console.log(`new sessions`); - console.log(sessions); - console.log("new session statuses"); - console.log(sessionResults); - if (status === "success") { - sessions.splice(index, 1); - sessionResults.splice(index, 1); - if (walletCodeSessions) walletCodeSessions.splice(index, 1); - if (codeFlowRequests) codeFlowRequests.splice(index, 1); - } - return status; - } - return null; -} - -function getPersonaFromAccessToken(accessToken, personas, accessTokens) { - let persona = null; - for (let i = 0; i < accessTokens.length; i++) { - console.log(accessTokens[i]) - if (accessTokens[i] === accessToken) { - persona = personas[i]; - } - } - return persona; -} - -async function validatePKCE(sessions, code, code_verifier, issuanceResults) { - for (let i = 0; i < sessions.length; i++) { - let element = sessions[i]; - if (code === element.sessionId) { - let challenge = element.challenge; - let tester = await base64UrlEncodeSha256(code_verifier); - if (tester === challenge) { - issuanceResults[i].status = "success"; - console.log("code flow status:" + issuanceResults[i].status); - } - } - } -} - -export default router; diff --git a/routes/verifierRoutes.js b/routes/verifierRoutes.js index fe0f66e..dcdae87 100644 --- a/routes/verifierRoutes.js +++ b/routes/verifierRoutes.js @@ -5,8 +5,11 @@ import { pemToJWK, generateNonce, decryptJWE, - buildVpRequestJwt, + buildVpRequestJSON, + buildVpRequestJWT, } from "../utils/cryptoUtils.js"; + +import { buildVPbyValue } from "../utils/tokenUtils.js"; import { decodeSdJwt, getClaims } from "@sd-jwt/decode"; import { digest } from "@sd-jwt/crypto-nodejs"; import qr from "qr-image"; @@ -18,12 +21,13 @@ import TimedArray from "../utils/timedArray.js"; const verifierRouter = express.Router(); const serverURL = process.env.SERVER_URL || "http://localhost:3000"; +const proxyPath = process.env.PROXY_PATH || null; const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); const presentation_definition_sdJwt = JSON.parse( - fs.readFileSync("./data/presentation_definition.json", "utf-8") + fs.readFileSync("./data/presentation_definition_sdjwt.json", "utf-8") ); const presentation_definition_jwt = JSON.parse( @@ -50,6 +54,10 @@ const presentation_definition_ferryboardingpass = JSON.parse( ) ); +const client_metadata = JSON.parse( + fs.readFileSync("./data/verifier-config.json", "utf-8") +); + const presentation_definition_alliance_and_education_Id = JSON.parse( fs.readFileSync( "./data/presentation_definition_alliance_and_education_Id.json", @@ -68,20 +76,38 @@ let sessions = []; let sessionHistory = new TimedArray(30000); //cache data for 30sec let verificationResultsHistory = new TimedArray(30000); //cache data for 30sec +/* ******************************************************* + CLIENT_ID_SCHEME_REDIRECT_URI + +*********************************************************** */ verifierRouter.get("/generateVPRequest", async (req, res) => { - const stateParam = req.query.id ? req.query.id : uuidv4(); + const stateParam = req.query.sessionId ? req.query.sessionId : uuidv4(); const nonce = generateNonce(16); - let request_uri = serverURL + "/vpRequest/" + stateParam; - const response_uri = serverURL + "/direct_post"; //not used + // The Verifier may send an Authorization Request as Request Object by value + // or by reference as defined in JWT-Secured Authorization Request (JAR) + // The Verifier articulates requirements of the Credential(s) that + // are requested using presentation_definition and presentation_definition_uri - const vpRequest = buildVP( - serverURL, - response_uri, - request_uri, - stateParam, - nonce, - encodeURIComponent(JSON.stringify(presentation_definition_sdJwt)) + // const uuid = req.params.id ? req.params.id : uuidv4(); + //url.searchParams.get("presentation_definition"); + const response_uri = serverURL + "/direct_post" + "/" + stateParam; + const presentation_definition_uri = + serverURL + "/presentation-definition/itbsdjwt"; + const client_metadata_uri = serverURL + "/client-metadata"; + const clientId = serverURL + "/direct_post" + "/" + stateParam; + sessions.push(stateParam); + verificationSessions.push({ + uuid: stateParam, + status: "pending", + claims: null, + }); + const vpRequest = buildVPbyValue( + clientId, + presentation_definition_uri, + "redirect_uri", + client_metadata_uri, + response_uri ); let code = qr.image(vpRequest, { @@ -101,17 +127,202 @@ verifierRouter.get("/generateVPRequest", async (req, res) => { // res.json({ vpRequest: vpRequest }); }); -verifierRouter.get("/vpRequest/:id", async (req, res) => { - console.log("VPRequest called Will send JWT"); - // console.log(jwtToken); +//PRESENTATION DEFINITON endpoint to support presentation_definition_uri Parameter */ +verifierRouter.get("/presentation-definition/:type", async (req, res) => { + const { type } = req.params; + const presentationDefinitions = { + 1: presentation_definition_jwt, + jwt: presentation_definition_jwt, + 2: presentation_definition_sdJwt, + itbsdjwt: presentation_definition_sdJwt, + 3: presentation_definition_pid, + pid: presentation_definition_pid, + 4: presentation_definition_epass, + epass: presentation_definition_epass, + 5: presentation_definition_alliance_and_education_Id, + eduId: presentation_definition_alliance_and_education_Id, + 6: presentation_definition_ferryboardingpass, // Changed from '5' to '6' to avoid duplication + ferryboarding: presentation_definition_ferryboardingpass, + }; + + // Retrieve the appropriate presentation definition based on the type + const selectedDefinition = presentationDefinitions[type]; + + if (selectedDefinition) { + res.type("application/json").send(selectedDefinition); + } else { + // Log the error for debugging purposes (optional) + console.error(`No presentation definition found for type: ${type}`); + + // Send a 500 Internal Server Error response + res.status(500).json({ + error: "Internal Server Error", + message: `No presentation definition found for type: ${type}`, + }); + } +}); + +// CLIENT VERIFIER METADATA +verifierRouter.get("/client-metadata", async (req, res) => { + res.type("application/json").send(clientMetadata); +}); + +/* ******************************************************* + CLIENT_ID_SCHEME x509_dns_san +*********************************************************** */ +verifierRouter.get("/generateVPRequestx509", async (req, res) => { + const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); + + let client_id = "dss.aegean.gr"; + let request_uri = `${serverURL}/x509VPrequest/${uuid}`; + let vpRequest = + "openid4vp://?client_id=" + + encodeURIComponent(client_id) + + "&request_uri=" + + encodeURIComponent(request_uri); + + console.log(`pushing to sessions ${uuid}`) + sessions.push(uuid); + verificationSessions.push({ + uuid: uuid, + status: "pending", + claims: null, + }); + console.log(`verification sessions`) + console.log(verificationSessions) + + let code = qr.image(vpRequest, { + type: "png", + ec_level: "M", + size: 20, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: vpRequest, + sessionId: uuid, + }); +}); + +verifierRouter.get("/x509VPrequest/:id", async (req, res) => { const uuid = req.params.id ? req.params.id : uuidv4(); + const response_uri = serverURL + "/direct_post" + "/" + uuid; - //url.searchParams.get("presentation_definition"); - const stateParam = uuidv4(); - const nonce = generateNonce(16); + const client_metadata = { + client_name: "UAegean EWC Verifier", + logo_uri: "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + location: "Greece", + cover_uri: "string", + description: "EWC pilot case verification", + vp_formats: { + jwt_vp: { + alg: ["EdDSA", "ES256K"], + }, + ldp_vp: { + proof_type: ["Ed25519Signature2018"], + }, + } + }; + + const clientId = "dss.aegean.gr"; + sessions.push(uuid); + verificationSessions.push({ + uuid: uuid, + status: "pending", + claims: null, + }); + + let signedVPJWT = await buildVpRequestJWT( + clientId, + response_uri, + presentation_definition_sdJwt, + "", + "x509_san_dns", + client_metadata, + null, + serverURL + ); + console.log(signedVPJWT); + res.type("text/plain").send(signedVPJWT); +}); + +/* ******************************************************* + CLIENT_ID_SCHEME did:jwks +*********************************************************** */ +verifierRouter.get("/generateVPRequestDidjwks", async (req, res) => { + const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); + + let contorller = serverURL; + if (proxyPath) { + contorller = serverURL.replace("/"+proxyPath,"") + ":" + proxyPath; + } + const client_id = `did:web:${contorller}`; + let request_uri = `${serverURL}/didjwks/${uuid}`; + let vpRequest = + "openid4vp://?client_id=" + + encodeURIComponent(client_id) + + "&request_uri=" + + encodeURIComponent(request_uri); + + console.log(`pushing to sessions ${uuid}`) + + sessions.push(uuid); + verificationSessions.push({ + uuid: uuid, + status: "pending", + claims: null, + }); + console.log(`verification sessions`) + console.log(verificationSessions) + + let code = qr.image(vpRequest, { + type: "png", + ec_level: "M", + size: 20, + margin: 10, + }); + let mediaType = "PNG"; + let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); + res.json({ + qr: encodedQR, + deepLink: vpRequest, + sessionId: uuid, + }); +}); + +verifierRouter.get("/didjwks/:id", async (req, res) => { + const uuid = req.params.id ? req.params.id : uuidv4(); const response_uri = serverURL + "/direct_post" + "/" + uuid; - let clientId = serverURL + "/direct_post" + "/" + uuid; + + const client_metadata = { + client_name: "UAegean EWC Verifier", + logo_uri: "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + location: "Greece", + cover_uri: "string", + description: "EWC pilot case verification", + vp_formats: { + jwt_vp: { + alg: ["EdDSA", "ES256K"], + }, + ldp_vp: { + proof_type: ["Ed25519Signature2018"], + }, + } + }; + + let privateKeyPem = fs.readFileSync( + "./didjwks/did_private_pkcs8.key", + "utf8" + ); + + let contorller = serverURL; + if (proxyPath) { + contorller = serverURL.replace("/"+proxyPath,"") + ":" + proxyPath; + } + const clientId = `did:web:${contorller}`; sessions.push(uuid); verificationSessions.push({ uuid: uuid, @@ -119,19 +330,25 @@ verifierRouter.get("/vpRequest/:id", async (req, res) => { claims: null, }); - let jwtToken = buildVpRequestJwt( - stateParam, - nonce, + let signedVPJWT = await buildVpRequestJWT( clientId, response_uri, presentation_definition_sdJwt, - jwks, - serverURL, - privateKey + privateKeyPem, + "did:jwks", + client_metadata, + `did:web:${contorller}#keys-1`, + serverURL ); - res.type("text/plain").send(jwtToken); + + console.log(signedVPJWT); + res.type("text/plain").send(signedVPJWT); }); +/* ******************************************** + RESPONSES +*******************************************/ + verifierRouter.post("/direct_post/:id", async (req, res) => { console.log("direct_post VP is below!"); const sessionId = req.params.id; @@ -140,7 +357,7 @@ verifierRouter.post("/direct_post/:id", async (req, res) => { if (sdjwt) { let presentationSubmission = req.body["presentation_submission"]; let state = req.body["state"]; - console.log(state); + // console.log(state); // console.log(response); const decodedSdJwt = await decodeSdJwt(sdjwt, digest); const claims = await getClaims( @@ -164,6 +381,52 @@ verifierRouter.post("/direct_post/:id", async (req, res) => { } }); +// this endpoint returns the autthorization requqest object (by reference), and is to be set on the request_uri +// however, when combined with client_id_scheme=redirect_uri then, the Authorization Request MUST NOT be signed, +// this causes incompatibilities with JAR, which is the expected result form this endpoint: +// The Verifier may send an Authorization Request as Request Object by value or by reference +// as defined in JWT-Secured Authorization Request (JAR) +// as a result this endpoint will not be used and we will only use request object by Value +/* + ******************DEPRECATED****************************************************************************** + */ +verifierRouter.get("/vpRequest/:id", async (req, res) => { + console.log("VPRequest called Will send JWT"); + // console.log(jwtToken); + const uuid = req.params.id ? req.params.id : uuidv4(); + + //url.searchParams.get("presentation_definition"); + const stateParam = uuidv4(); + const nonce = generateNonce(16); + + const response_uri = serverURL + "/direct_post" + "/" + uuid; + let clientId = serverURL + "/direct_post" + "/" + uuid; + sessions.push(uuid); + verificationSessions.push({ + uuid: uuid, + status: "pending", + claims: null, + }); + + let client_id_scheme = "redirect_uri"; + let jwtToken = buildVpRequestJWT( + clientId, + response_uri, + presentation_definition_sdJwt, + privateKey, + client_id_scheme, + clientMetadata, + null, + serverURL + ); + + console.log("VP request "); + //console.log(JSON.stringify(jwtToken, null, 2)); + console.log(jwtToken); + + res.type("text/plain").send(jwtToken); +}); + // ******************************************************* verifierRouter.get("/generateVPRequest-jwt", async (req, res) => { @@ -236,15 +499,6 @@ verifierRouter.get("/vpRequestJwt/:id", async (req, res) => { res.json(vpRequest); }); -verifierRouter.get("/presentation-definition/:type", async (req, res) => { - const { type } = req.params; - if (type == 1) { - res.type("application/json").send(presentation_definition_jwt); - } - console.log("ERROR getting presentatiton-definition type"); - res.status(500); -}); - // *******************PILOT USE CASES ****************************** verifierRouter.get("/vp-request/:type", async (req, res) => { const { type } = req.params; @@ -309,27 +563,29 @@ verifierRouter.get("/vpRequest/:type/:id", async (req, res) => { } else { return res.status(400).type("text/plain").send("Invalid type parameter"); } + /* + TODO the vpRequest/:type/:id endpoint defined in routes/verifierRoutes.js, + that is used for JWT secured Authorization Requests (JAR), + returns a raw JSON object instead of a JWT. + + According to the JAR standard (RFC9101), the endpoint defined in request_uri should present a JWT in the response body, + never raw JSON. + Also, as has been mentioned before, when a client_id_scheme of redirect_uri is used, + the authorization request must not be signed, + so perhaps the conformance backend shouldn't use JAR at all with this scheme. +*/ - // let jwtToken = buildVpRequestJwt( - // stateParam, - // nonce, - // clientId, - // response_uri, - // presentationDefinition, - // jwks, - // serverURL, - // privateKey - // ); let vpRequest = { client_metadata: { client_name: "UAegean EWC Verifier", - logo_uri: "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", + logo_uri: + "https://studyingreece.edu.gr/wp-content/uploads/2023/03/25.png", location: "Greece", cover_uri: "string", description: "EWC pilot case verification", }, client_id: clientId, - client_id_scheme: "redirect_uri", + client_id_scheme: "redirect_uri", //TODO change this response_uri: response_uri, response_type: "vp_token", response_mode: "direct_post", diff --git a/server.js b/server.js index c045b68..d803744 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,6 @@ // main file import express from "express"; -import router from "./routes/routes.js"; +import router from "./routes/preAuthSDjwRoutes.js"; import verifierRouter from "./routes/verifierRoutes.js"; import metadataRouter from "./routes/metadataroutes.js"; import codeFlowRouter from "./routes/codeFlowJwtRoutes.js"; @@ -8,9 +8,17 @@ import codeFlowRouterSDJWT from "./routes/codeFlowSdJwtRoutes.js"; import boardingPassRouter from "./routes/boardingPassRoutes.js"; import pidRouter from "./routes/pidroutes.js"; import passportRouter from "./routes/passportRoutes.js"; +import didWebRouter from "./routes/didweb.js" import educationalRouter from "./routes/educationalRoutes.js"; import bodyParser from "body-parser"; // Body parser middleware +import * as OpenApiValidator from "express-openapi-validator"; + +import path from "path"; + +// Path to your OpenAPI spec file (JSON or YAML) +const apiSpec = path.join(process.cwd(), "openapi.yaml"); + const app = express(); const port = 3000; @@ -24,6 +32,17 @@ app.use((req, res, next) => { ${req.method} ${req.url}`); next(); // Pass control to the next middleware function }); + + +// const apiSpecPath = path.join(process.cwd(), "openapi.yml"); +// app.use( +// OpenApiValidator.middleware({ +// apiSpec: apiSpecPath, +// validateRequests: false, // (default) +// validateResponses: false, // false by default +// // ignorePaths: [/^\/offer-code-sd-jwt$/, /^\/offer-no-code$/, /^\/offer-tx-code$/], +// }) +// ); app.use("/", router); app.use("/", verifierRouter); app.use("/", metadataRouter); @@ -33,6 +52,16 @@ app.use("/", pidRouter); app.use("/", passportRouter); app.use("/", educationalRouter); app.use("/", boardingPassRouter); +app.use("/", didWebRouter); + +// Error handler for validation errors +app.use((err, req, res, next) => { + // Format error response + res.status(err.status || 500).json({ + message: err.message, + errors: err.errors, + }); +}); // Start the server app.listen(port, () => { diff --git a/services/cacheService.js b/services/cacheService.js index 7b3a4f3..449445e 100644 --- a/services/cacheService.js +++ b/services/cacheService.js @@ -8,6 +8,10 @@ let issuerCodeSessions = []; let codeFlowRequests = []; let codeFlowRequestsResults = []; +let pushedAuthorizationRequests = new Map(); +let sessionsAuthorizationDetail = new Map(); +let authCodeAuthorizationDetail = new Map(); + export function getPreCodeSessions() { return { sessions: sessions, @@ -25,3 +29,17 @@ export function getAuthCodeSessions() { results: codeFlowRequestsResults, }; } + +export function getPushedAuthorizationRequests() { + return pushedAuthorizationRequests; +} + + +export function getSessionsAuthorizationDetail() { + return sessionsAuthorizationDetail; +} + + +export function getAuthCodeAuthorizationDetail() { + return authCodeAuthorizationDetail; +} \ No newline at end of file diff --git a/utils/credPayloadUtil.js b/utils/credPayloadUtil.js new file mode 100644 index 0000000..ee58d68 --- /dev/null +++ b/utils/credPayloadUtil.js @@ -0,0 +1,802 @@ +import { getCredentialSubjectForPersona } from "./personasUtils.js"; + +// Helper functions to create payloads for different credential types + +export const createPIDPayload = (token, serverURL, decodedHeaderSubjectDID) => { + const preSessions = getPreCodeSessions(); + const persona = getPersonaFromAccessToken( + token, + preSessions.personas, + preSessions.accessTokens + ); + const credentialSubject = getCredentialSubjectForPersona( + persona, + decodedHeaderSubjectDID + ); + + return { + iss: serverURL, + sub: decodedHeaderSubjectDID || "", + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, // Token expiration time (30 days) + iat: Math.floor(Date.now() / 1000), + jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", + vc: { + credentialSubject: credentialSubject, + expirationDate: new Date( + (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 + ).toISOString(), + id: decodedHeaderSubjectDID, + issuanceDate: new Date( + Math.floor(Date.now() / 1000) * 1000 + ).toISOString(), + issuer: serverURL, + type: ["PID"], + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://europa.eu/2018/credentials/eudi/pid/v1", + ], + validFrom: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), + }, + }; +}; + +export const createEPassportPayload = (serverURL, decodedHeaderSubjectDID) => { + return { + iss: serverURL, + sub: decodedHeaderSubjectDID || "", + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 60 * 60, // Token expiration time (1 hour) + jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", + vc: { + credentialSubject: { + id: decodedHeaderSubjectDID || "", + electronicPassport: { + dataGroup1: { + birthdate: "1990-01-01", + docTypeCode: "P", + expiryDate: "2030-01-01", + genderCode: "M", + holdersName: "John Doe", + issuerCode: "GR", + natlText: "Hellenic", + passportNumberIdentifier: "123456789", + }, + dataGroup15: { + activeAuthentication: { + publicKeyBinaryObject: "somePublicKeyUri", + }, + }, + dataGroup2EncodedFaceBiometrics: { + faceBiometricDataEncodedPicture: "someBiometricUri", + }, + digitalTravelCredential: { + contentInfo: { + versionNumber: 1, + signatureInfo: { + digestHashAlgorithmIdentifier: "sha-256", + signatureAlgorithmIdentifier: "RS256", + signatureCertificateText: "someCertificateText", + signatureDigestResultBinaryObject: "someDigestResultUri", + signedAttributes: { + attributeTypeCode: "someTypeCode", + attributeValueText: "someValueText", + }, + }, + }, + dataCapabilitiesInfo: { + dataTransferInterfaceTypeCode: "NFC", + securityAssuranceLevelIndText: "someSecurityLevel", + userConsentInfoText: "userConsentRequired", + virtualComponentPresenceInd: true, + }, + dataContent: { + dataGroup1: { + birthdate: "1990-01-01", + docTypeCode: "P", + expiryDate: "2030-01-01", + genderCode: "M", + holdersName: "John Doe", + issuerCode: "GR", + natlText: "Hellenic", + passportNumberIdentifier: "123456789", + personalNumberIdentifier: "987654321", + }, + dataGroup2EncodedFaceBiometrics: { + faceBiometricDataEncodedPicture: "someBiometricUri", + }, + docSecurityObject: { + dataGroupHash: [ + { dataGroupNumber: 1, valueBinaryObject: "someHashUri" }, + ], + digestHashAlgorithmIdentifier: "sha-256", + versionNumber: 1, + }, + }, + }, + }, + }, + type: ["ePassportCredential"], + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://schemas.prod.digitalcredentials.iata.org/contexts/iata_credential.jsonld", + ], + issuer: serverURL, + validFrom: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), + }, + }; +}; + +export const createStudentIDPayload = ( + token, + serverURL, + decodedHeaderSubjectDID +) => { + const preSessions = getPreCodeSessions(); + const persona = getPersonaFromAccessToken( + token, + preSessions.personas, + preSessions.accessTokens + ); + + let credentialSubject = { + id: decodedHeaderSubjectDID || "", + identifier: "john.doe@university.edu", + schacPersonalUniqueCode: [ + "urn:schac:personalUniqueCode:int:esi:university.edu:12345", + ], + schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", + schacHomeOrganization: "university.edu", + familyName: "Doe", + firstName: "John", + displayName: "John Doe", + dateOfBirth: "1990-01-01", + commonName: "Johnathan Doe", + mail: "john.doe@university.edu", + eduPersonPrincipalName: "john.doe@university.edu", + eduPersonPrimaryAffiliation: "student", + eduPersonAffiliation: ["member", "student"], + eduPersonScopedAffiliation: ["student@university.edu"], + eduPersonAssurance: [ + "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", + ], + }; + + // Handle different persona data if available + if (persona === "1") { + credentialSubject = { + ...credentialSubject, + identifier: "mario.conti@ewc.eu", + familyName: "Conti", + firstName: "Mario", + displayName: "Mario Conti", + commonName: "Mario Conti", + mail: "mario.conti@ewc.eu", + }; + } else if (persona === "2") { + credentialSubject = { + ...credentialSubject, + identifier: "hannah@ewc.eu", + familyName: "Matkalainen", + firstName: "Hannah", + displayName: "Hannah Matkalainen", + commonName: "Hannah Matkalainen", + mail: "hannah@ewc.eu", + }; + } else if (persona === "3") { + credentialSubject = { + ...credentialSubject, + identifier: "felix@ewc.eu", + familyName: "Fischer", + firstName: "Felix", + displayName: "Felix Fischer", + commonName: "Felix Fischer", + mail: "felix@ewc.eu", + }; + } + + return { + iss: serverURL, + sub: decodedHeaderSubjectDID || "", + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, + iat: Math.floor(Date.now() / 1000), + jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", + vc: { + type: ["StudentID"], + "@context": ["https://www.w3.org/2018/credentials/v1"], + issuer: serverURL, + credentialSubject: credentialSubject, + issuanceDate: new Date( + Math.floor(Date.now() / 1000) * 1000 + ).toISOString(), + expirationDate: new Date( + (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 + ).toISOString(), + validFrom: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), + }, + }; +}; + +export const createAllianceIDPayload = (serverURL, decodedHeaderSubjectDID) => { + return { + iss: serverURL, + sub: decodedHeaderSubjectDID || "", + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, + iat: Math.floor(Date.now() / 1000), + jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", + vc: { + type: ["allianceIDCredential"], + "@context": ["https://www.w3.org/2018/credentials/v1"], + issuer: serverURL, + credentialSubject: { + id: decodedHeaderSubjectDID || "", + identifier: { + schemeID: "European Student Identifier", + value: + "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", + id: "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", + }, + }, + issuanceDate: new Date( + Math.floor(Date.now() / 1000) * 1000 + ).toISOString(), + expirationDate: new Date( + (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 + ).toISOString(), + validFrom: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), + }, + }; +}; + +export const createFerryBoardingPassPayload = ( + token, + serverURL, + decodedHeaderSubjectDID +) => { + const preSessions = getPreCodeSessions(); + const persona = getPersonaFromAccessToken( + token, + preSessions.personas, + preSessions.accessTokens + ); + + let credentialSubject = { + id: decodedHeaderSubjectDID || "", + identifier: "John Doe", + ticketQR: "data:image/png;base64,someBase64EncodedQR", + ticketNumber: "ABC123456789", + ticketLet: "A", + lastName: "Doe", + firstName: "John", + seatType: "Economy", + seatNumber: "12A", + departureDate: "2023-11-30", + departureTime: "13:07:34", + arrivalDate: "2023-11-30", + arrivalTime: "15:30:00", + arrivalPort: "NYC", + vesselDescription: "Ferry XYZ", + }; + + if (persona === "1") { + credentialSubject = { + ...credentialSubject, + identifier: "Mario Conti", + lastName: "Conti", + firstName: "Mario", + ticketNumber: "3022", + arrivalPort: "Mykonos", + vesselDescription: "Ferry to Mykonos", + }; + } else if (persona === "2") { + credentialSubject = { + ...credentialSubject, + identifier: "Hannah Matkalainen", + lastName: "Matkalainen", + firstName: "Hannah", + ticketNumber: "3022", + arrivalPort: "Santorini", + vesselDescription: "Ferry to Santorini", + }; + } else if (persona === "3") { + credentialSubject = { + ...credentialSubject, + identifier: "Felix Fischer", + lastName: "Fischer", + firstName: "Felix", + ticketNumber: "3022", + arrivalPort: "Crete", + vesselDescription: "Ferry to Crete", + }; + } + + return { + iss: serverURL, + sub: decodedHeaderSubjectDID || "", + exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, + iat: Math.floor(Date.now() / 1000), + jti: "urn:did:1904a925-38bd-4eda-b682-4b5e3ca9d4bc", + vc: { + type: ["VerifiableCredential", "ferryBoardingPassCredential"], + "@context": ["https://www.w3.org/2018/credentials/v1"], + issuer: serverURL, + credentialSubject: credentialSubject, + issuanceDate: new Date( + Math.floor(Date.now() / 1000) * 1000 + ).toISOString(), + expirationDate: new Date( + (Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30) * 1000 + ).toISOString(), + validFrom: new Date(Math.floor(Date.now() / 1000) * 1000).toISOString(), + }, + }; +}; + +// SD-JWT HELPERS + +export const getPIDSDJWTData = (decodedHeaderSubjectDID) => { + const currentTimestamp = new Date().getTime(); + const currentDate = new Date(); + const expTimestamp = currentDate.setFullYear(currentDate.getFullYear() + 1); + const claims = { + id: decodedHeaderSubjectDID || "", + given_name: "John", + family_name: "Doe", + birth_date: "1990-01-01", + age_over_18: true, + issuance_date: currentTimestamp, + expiry_date: expTimestamp, //expTimestamp.getTime(), + issuing_authority: "UAegean Test Issuer", + issuing_country: "Greece", + }; + + const disclosureFrame = { + _sd: [ + "id", + "given_name", + "family_name", + "birth_date", + "age_over_18", + "expiry_date", + "issuance_date", + "issuing_authority", + "issuing_country", + ], + }; + + return { claims, disclosureFrame }; +}; + +export const getStudentIDSDJWTData = (decodedHeaderSubjectDID) => { + const claims = { + id: decodedHeaderSubjectDID || "", + identifier: "john.doe@university.edu", + schacPersonalUniqueCode: [ + "urn:schac:personalUniqueCode:int:esi:university.edu:12345", + ], + schacPersonalUniqueID: "urn:schac:personalUniqueID:us:12345", + schacHomeOrganization: "university.edu", + familyName: "Doe", + firstName: "John", + displayName: "John Doe", + dateOfBirth: "1990-01-01", + commonName: "Johnathan Doe", + mail: "john.doe@university.edu", + eduPersonPrincipalName: "john.doe@university.edu", + eduPersonPrimaryAffiliation: "student", + eduPersonAffiliation: ["member", "student"], + eduPersonScopedAffiliation: ["student@university.edu"], + eduPersonAssurance: [ + "https://wiki.refeds.org/display/ASS/REFEDS+Assurance+Framework+ver+1.0", + ], + }; + + const disclosureFrame = { + _sd: [ + "id", + "identifier", + "schacPersonalUniqueCode", + "schacPersonalUniqueID", + "schacHomeOrganization", + "familyName", + "firstName", + "displayName", + "dateOfBirth", + "commonName", + "mail", + "eduPersonPrincipalName", + "eduPersonPrimaryAffiliation", + "eduPersonAffiliation", + "eduPersonScopedAffiliation", + "eduPersonAssurance", + ], + }; + + return { claims, disclosureFrame }; +}; + +export const getAllianceIDSDJWTData = (decodedHeaderSubjectDID) => { + const claims = { + id: decodedHeaderSubjectDID || "", + identifier: { + schemeID: "European Student Identifier", + value: + "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", + id: "urn:schac:europeanUniversityAllianceCode:int:euai:ERUA:universityXYZ", + }, + }; + + const disclosureFrame = { + _sd: ["id", "identifier.schemeID", "identifier.value", "identifier.id"], + }; + + return { claims, disclosureFrame }; +}; + +export const getFerryBoardingPassSDJWTData = (decodedHeaderSubjectDID) => { + const claims = { + id: decodedHeaderSubjectDID || "", + identifier: "John Doe", + ticketQR: "data:image/png;base64,someBase64EncodedQR", + ticketNumber: "ABC123456789", + ticketLet: "A", + lastName: "Doe", + firstName: "John", + seatType: "Economy", + seatNumber: "12A", + departureDate: "2023-11-30", + departureTime: "13:07:34", + arrivalDate: "2023-11-30", + arrivalTime: "15:30:00", + arrivalPort: "NYC", + vesselDescription: "Ferry XYZ", + }; + + const disclosureFrame = { + _sd: [ + "id", + "identifier", + "ticketQR", + "ticketNumber", + "ticketLet", + "lastName", + "firstName", + "seatType", + "seatNumber", + "departureDate", + "departureTime", + "arrivalDate", + "arrivalTime", + "arrivalPort", + "vesselDescription", + ], + }; + + return { claims, disclosureFrame }; +}; + +export const getGenericSDJWTData = (decodedHeaderSubjectDID, credType) => { + const claims = { + id: decodedHeaderSubjectDID || "", + given_name: "John", + last_name: "Doe", + }; + + const disclosureFrame = { + _sd: ["id", "given_name", "last_name"], + }; + + return { claims, disclosureFrame }; +}; + +export const getEPassportSDJWTData = (decodedHeaderSubjectDID) => { + const claims = { + id: decodedHeaderSubjectDID || "", + electronicPassport: { + dataGroup1: { + birthdate: "1990-01-01", + docTypeCode: "P", + expiryDate: "2030-01-01", + genderCode: "M", + holdersName: "John Doe", + issuerCode: "GR", + natlText: "Hellenic", + passportNumberIdentifier: "123456789", + }, + dataGroup15: { + activeAuthentication: { + publicKeyBinaryObject: "somePublicKeyUri", + }, + }, + dataGroup2EncodedFaceBiometrics: { + faceBiometricDataEncodedPicture: "someBiometricUri", + }, + }, + }; + + const disclosureFrame = { + _sd: [ + "id", + "electronicPassport.dataGroup1.birthdate", + "electronicPassport.dataGroup1.docTypeCode", + "electronicPassport.dataGroup1.expiryDate", + "electronicPassport.dataGroup1.genderCode", + "electronicPassport.dataGroup1.holdersName", + "electronicPassport.dataGroup1.issuerCode", + "electronicPassport.dataGroup1.natlText", + "electronicPassport.dataGroup1.passportNumberIdentifier", + ], + }; + + return { claims, disclosureFrame }; +}; + +export const getVReceiptSDJWTData = (decodedHeaderSubjectDID) => { + const claims = { + id: decodedHeaderSubjectDID || "", + + // Monetary Total + "MonetaryTotal.lineExtensionAmount": 150.75, + "MonetaryTotal.taxInclusiveAmount": 180.9, + "MonetaryTotal.payableAmount": 180.9, + + // Tax Total + "TaxTotal.taxSubtotal_": { + taxableAmount: 150.75, + taxSubtotalTaxAmount: 30.15, + taxCategory_: "VAT", + percent: 20, + }, + "TaxTotal.taxAmount": 30.15, + + // Address + "Address.streetName": "123 Main Street", + "Address.cityName": "Sample City", + "Address.postcode": "SC12345", + "Address.countryIdentifier": "GB", + + // Tax Category + "TaxCategory.taxScheme_": "VAT Scheme", + + // Item Property + "ItemProperty.itemPropertyName": "Color", + "ItemProperty.value": "Red", + + // Tax Scheme + "TaxScheme.taxSchemeName": "Standard VAT", + + // Allowance Charge + "AllowanceCharge.amount": 10.0, + "AllowanceCharge.allowanceChargeReason": "Discount Applied", + + // Party Name + "PartyName.name": "Sample Seller Ltd.", + + // Payment Means + "PaymentMeans.cardAccount_": { + networkID: "VISA", + accountNumberID: "411111******1111", + }, + "PaymentMeans.paymentMeansCode": "PM01", + + // Party Identification + "PartyIdentification.iD": "PID123456789", + + // Purchase Receipt + "PurchaseReceipt.paymentMeans_": "Credit Card", + "PurchaseReceipt.note": "Thank you for your purchase!", + "PurchaseReceipt.delivery_": "Standard Shipping", + "PurchaseReceipt.taxIncludedIndicator": true, + "PurchaseReceipt.taxTotal_": 30.15, + "PurchaseReceipt.accountingCustomerParty_": "Customer Account", + "PurchaseReceipt.documentCurrencyCode": "GBP", + "PurchaseReceipt.payment_": "Paid", + "PurchaseReceipt.sellerSupplierParty": "Sample Seller Ltd.", + "PurchaseReceipt.legalMonetaryTotal": 180.9, + "PurchaseReceipt.salesDocumentReference": "SDR123456", + "PurchaseReceipt.iD": "PR123456789", + "PurchaseReceipt.issueDate": "2024-04-27", + "PurchaseReceipt.purchaseReceiptLine-1": "Line1", + + // Tax Subtotal + "TaxSubtotal.taxableAmount": 150.75, + "TaxSubtotal.taxSubtotalTaxAmount": 30.15, + "TaxSubtotal.taxCategory_": "VAT", + "TaxSubtotal.percent": 20, + + // Item + "Item.commodityClassification_": "CC123", + "Item.itemInstance_": "Instance1", + "Item.additionalItemProperty": "Property1", + + // Payment + "Payment.authorizationID": "AUTH123456789", + "Payment.paidAmount": 180.9, + "Payment.transactionID": "TXN123456789", + + // Supplier Party + "SupplierParty.party_": "SupplierParty1", + "SupplierParty.supplierPartyID": "SPID123456", + + // Party + "Party.partyIdentification_.iD": "PID987654321", + "Party.partyName_.name": "Sample Buyer Ltd.", + "Party.postalAddress_.streetName": "456 Another St.", + "Party.postalAddress_.cityName": "Another City", + "Party.postalAddress_.postcode": "AC54321", + "Party.postalAddress_.countryIdentifier": "GB", + + // Commodity Classification + "CommodityClassification.itemClassificationCode": "ICC12345", + + // Card Account + "CardAccount.networkID": "MASTERCARD", + "CardAccount.accountNumberID": "550000******0004", + + // Customer Party + "CustomerParty.party_.partyIdentification_.iD": "CPID123456789", + "CustomerParty.party_.partyName_.name": "Customer Name Ltd.", + "CustomerParty.party_.postalAddress_.streetName": "789 Customer Ave.", + "CustomerParty.party_.postalAddress_.cityName": "Customer City", + "CustomerParty.party_.postalAddress_.postcode": "CC67890", + "CustomerParty.party_.postalAddress_.countryIdentifier": "GB", + + // Purchase Receipt Line + "PurchaseReceiptLine.item_.commodityClassification_.itemClassificationCode": + "ICC67890", + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.itemPropertyName": + "Size", + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.value": + "Medium", + "PurchaseReceiptLine.quantity": 2, + "PurchaseReceiptLine.allowanceCharge_.amount": 5.0, + "PurchaseReceiptLine.allowanceCharge_.allowanceChargeReason": "Promotion", + "PurchaseReceiptLine.taxInclusiveLineExtentionAmount": 80.45, + "PurchaseReceiptLine.iD": "PRL123456789", + + // Delivery + "Delivery.actualDeliveryDate": "2024-04-28", + "Delivery.deliveryAddress.streetName": "321 Delivery Rd.", + "Delivery.deliveryAddress.cityName": "Delivery City", + "Delivery.deliveryAddress.postcode": "DC12345", + "Delivery.deliveryAddress.countryIdentifier": "GB", + "Delivery.actualDeliveryTime": "14:30", + + // Document Reference + "DocumentReference.iD": "DR123456789", + + // Item Instance + "ItemInstance.additionalItemProperty.itemPropertyName": "Warranty", + "ItemInstance.additionalItemProperty.value": "2 Years", + }; + + const disclosureFrame = { + _sd: [ + "id", + + // Monetary Total + "MonetaryTotal.lineExtensionAmount", + "MonetaryTotal.taxInclusiveAmount", + "MonetaryTotal.payableAmount", + + // Tax Total + "TaxTotal.taxSubtotal_", + "TaxTotal.taxAmount", + + // Address + "Address.streetName", + "Address.cityName", + "Address.postcode", + "Address.countryIdentifier", + + // Tax Category + "TaxCategory.taxScheme_", + + // Item Property + "ItemProperty.itemPropertyName", + "ItemProperty.value", + + // Tax Scheme + "TaxScheme.taxSchemeName", + + // Allowance Charge + "AllowanceCharge.amount", + "AllowanceCharge.allowanceChargeReason", + + // Party Name + "PartyName.name", + + // Payment Means + "PaymentMeans.cardAccount_", + "PaymentMeans.paymentMeansCode", + + // Party Identification + "PartyIdentification.iD", + + // Purchase Receipt + "PurchaseReceipt.paymentMeans_", + "PurchaseReceipt.note", + "PurchaseReceipt.delivery_", + "PurchaseReceipt.taxIncludedIndicator", + "PurchaseReceipt.taxTotal_", + "PurchaseReceipt.accountingCustomerParty_", + "PurchaseReceipt.documentCurrencyCode", + "PurchaseReceipt.payment_", + "PurchaseReceipt.sellerSupplierParty", + "PurchaseReceipt.legalMonetaryTotal", + "PurchaseReceipt.salesDocumentReference", + "PurchaseReceipt.iD", + "PurchaseReceipt.issueDate", + "PurchaseReceipt.purchaseReceiptLine-1", + + // Tax Subtotal + "TaxSubtotal.taxableAmount", + "TaxSubtotal.taxSubtotalTaxAmount", + "TaxSubtotal.taxCategory_", + "TaxSubtotal.percent", + + // Item + "Item.commodityClassification_", + "Item.itemInstance_", + "Item.additionalItemProperty", + + // Payment + "Payment.authorizationID", + "Payment.paidAmount", + "Payment.transactionID", + + // Supplier Party + "SupplierParty.party_", + "SupplierParty.supplierPartyID", + + // Party + "Party.partyIdentification_.iD", + "Party.partyName_.name", + "Party.postalAddress_.streetName", + "Party.postalAddress_.cityName", + "Party.postalAddress_.postcode", + "Party.postalAddress_.countryIdentifier", + + // Commodity Classification + "CommodityClassification.itemClassificationCode", + + // Card Account + "CardAccount.networkID", + "CardAccount.accountNumberID", + + // Customer Party + "CustomerParty.party_.partyIdentification_.iD", + "CustomerParty.party_.partyName_.name", + "CustomerParty.party_.postalAddress_.streetName", + "CustomerParty.party_.postalAddress_.cityName", + "CustomerParty.party_.postalAddress_.postcode", + "CustomerParty.party_.postalAddress_.countryIdentifier", + + // Purchase Receipt Line + "PurchaseReceiptLine.item_.commodityClassification_.itemClassificationCode", + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.itemPropertyName", + "PurchaseReceiptLine.item_.itemInstance_.additionalItemProperty.value", + "PurchaseReceiptLine.quantity", + "PurchaseReceiptLine.allowanceCharge_.amount", + "PurchaseReceiptLine.allowanceCharge_.allowanceChargeReason", + "PurchaseReceiptLine.taxInclusiveLineExtentionAmount", + "PurchaseReceiptLine.iD", + + // Delivery + "Delivery.actualDeliveryDate", + "Delivery.deliveryAddress.streetName", + "Delivery.deliveryAddress.cityName", + "Delivery.deliveryAddress.postcode", + "Delivery.deliveryAddress.countryIdentifier", + "Delivery.actualDeliveryTime", + + // Document Reference + "DocumentReference.iD", + + // Item Instance + "ItemInstance.additionalItemProperty.itemPropertyName", + "ItemInstance.additionalItemProperty.value", + ], + }; + + return { claims, disclosureFrame }; +}; diff --git a/utils/cryptoUtils.js b/utils/cryptoUtils.js index b740802..d257355 100644 --- a/utils/cryptoUtils.js +++ b/utils/cryptoUtils.js @@ -1,6 +1,9 @@ import crypto from "crypto"; import jwt from "jsonwebtoken"; import * as jose from "jose"; +import base64url from "base64url"; +import { error } from "console"; +import fs from "fs"; export function pemToJWK(pem, keyType) { let key; @@ -27,7 +30,7 @@ export function generateNonce(length = 12) { return crypto.randomBytes(length).toString("hex"); } -export function buildVpRequestJwt( +export function buildVpRequestJSON( state, nonce, client_id, @@ -70,18 +73,21 @@ export function buildVpRequestJwt( presentation_definition: presentation_definition, redirect_uri: response_uri, // response_mode: "direct_post", - client_metadata : "", - + nonce: "n-0S6_WzA2Mj", + state: "af0ifjsldkj", + client_metadata: { + vp_formats: { + jwt_vp: { + alg: ["EdDSA", "ES256K"], + }, + ldp_vp: { + proof_type: ["Ed25519Signature2018"], + }, + }, + // Add any additional required metadata here + }, + // response_uri: response_uri, //TODO Note: If the Client Identifier scheme redirect_uri is used in conjunction with the Response Mode direct_post, and the response_uri parameter is present, the client_id value MUST be equal to the response_uri value - // iss: serverURL, - // state: state, - // exp: Math.floor(Date.now() / 1000) + 60, - // nonce: nonce, - // iat: Math.floor(Date.now() / 1000), - // nbf: Math.floor(Date.now() / 1000), - // redirect_uri: redirect_uri, - // scope: "openid", - }; // const header = { @@ -97,6 +103,130 @@ export function buildVpRequestJwt( return jwtPayload; } +export async function buildVpRequestJWT( + client_id, + redirect_uri, + presentation_definition, + privateKey = "", + client_id_scheme = "redirect_uri", // Default to "redirect_uri" + client_metadata = {}, + kid =null, // Default to an empty object, + serverURL +) { + const nonce = generateNonce(16); + const state = generateNonce(16); + + if (client_id_scheme === "x509_san_dns") { + privateKey = fs.readFileSync("./x509/client_private_pkcs8.key", "utf8"); + const certificate = fs.readFileSync( + "./x509/client_certificate.crt", + "utf8" + ); + // Convert certificate to Base64 without headers + const certBase64 = certificate + .replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replace(/\s+/g, ""); + + // The result is a JWS-signed JWT [RFC7519]. If signed, the Authorization Request Object SHOULD contain + // the Claims iss (issuer) and aud (audience) as members with their semantics being the same as defined in the JWT [RFC7519] specification. + // The value of aud should be the value of the authorization server (AS) issuer, as defined in RFC 8414 [RFC8414] + + // Construct the JWT payload + let jwtPayload = { + response_type: "vp_token", + response_mode: "direct_post", + client_id: client_id, // this should match the dns record in the certificate (dss.aegean.gr) + client_id_scheme: client_id_scheme, + presentation_definition: presentation_definition, + response_uri: redirect_uri, + nonce: nonce, + state: state, + client_metadata: client_metadata, // + iss: serverURL, + aud: serverURL + }; + + // Define the JWT header + // const header = { + // alg: "ES256", + // kid: `aegean#authentication-key`, // Ensure this kid is resolvable from the did.json endpoint + // }; + const header = { + alg: "RS256", + typ: "JWT", + x5c: [certBase64], + }; + + const jwt = await new jose.SignJWT(jwtPayload) + .setProtectedHeader(header) + .sign(await jose.importPKCS8(privateKey, "RS256")); + + return jwt; + + // Conditional signing based on client_id_scheme + + //} + // else if (client_id_scheme !== "redirect_uri") { + // // Do NOT sign the JWT for "redirect_uri" scheme + // // Sign the JWT as per the scheme's requirements + // const token = jwt.sign(jwtPayload, privateKey, { + // algorithm: "ES256", + // noTimestamp: true, // Retain if necessary + // header, + // }); + // return token; + + // return jwtPayload; + // } + } else if (client_id_scheme === "did:jwks") { + + const signingKey = { + kty: "EC", + x: "ijVgOGHvwHSeV1Z2iLF9pQLQAw7KcHF3VIjThhvVtBQ", + y: "SfFShWAUGEnNx24V2b5G1jrhJNHmMwtgROBOi9OKJLc", + crv: "P-256", + use: "sig", + kid: kid, + }; + + // Convert the private key to a KeyLike object + const privateKeyObj = await jose.importPKCS8( + privateKey, + signingKey.alg || "ES256" + ); + + const jwtPayload = { + response_type: "vp_token", + response_mode: "direct_post", + client_id: client_id, // DID the did of the verifier!!!!!! + client_id_scheme: client_id_scheme, + presentation_definition: presentation_definition, + redirect_uri: redirect_uri, + nonce: nonce, + state: state, + client_metadata: client_metadata, + }; + + // JWT header + const header = { + alg: signingKey.alg || "ES256", + typ: "JWT", + kid: kid, + }; + + const jwt = await new jose.SignJWT(jwtPayload) + .setProtectedHeader(header) + .sign(privateKeyObj); + + return jwt; + + // Conditional signing based on client_id_scheme + } else { + throw new Error("not supported client_id_scheme:" + client_id_scheme); + } +} + export async function decryptJWE(jweToken, privateKeyPEM) { try { const privateKey = crypto.createPrivateKey(privateKeyPEM); @@ -116,14 +246,13 @@ export async function decryptJWE(jweToken, privateKeyPEM) { } } - export async function base64UrlEncodeSha256(codeVerifier) { // Convert the code verifier string to an ArrayBuffer with ASCII encoding const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); // Calculate the SHA-256 hash of the ArrayBuffer - const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashBuffer = await crypto.subtle.digest("sha-256", data); // Convert the ArrayBuffer to a Uint8Array const hashArray = new Uint8Array(hashBuffer); @@ -132,7 +261,10 @@ export async function base64UrlEncodeSha256(codeVerifier) { const base64String = btoa(String.fromCharCode.apply(null, hashArray)); // Convert Base64 to Base64URL by replacing '+' with '-', '/' with '_', and stripping '=' - const base64UrlString = base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); + const base64UrlString = base64String + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); return base64UrlString; } diff --git a/utils/didjwks.js b/utils/didjwks.js new file mode 100644 index 0000000..8c564df --- /dev/null +++ b/utils/didjwks.js @@ -0,0 +1,16 @@ +import crypto from "crypto"; +import jwt from "jsonwebtoken"; +import { importSPKI, exportJWK } from "jose"; +import base64url from "base64url"; +import { error } from "console"; +import fs from "fs"; + + + +export async function convertPemToJwk() { + const spki = fs.readFileSync('./didjwks/did_public.pem', 'utf8'); + const key = await importSPKI(spki, 'ES256'); + const jwk = await exportJWK(key); + return jwk +// console.log(JSON.stringify(jwk, null, 2)); +} \ No newline at end of file diff --git a/utils/personasUtils.js b/utils/personasUtils.js new file mode 100644 index 0000000..c3dba3b --- /dev/null +++ b/utils/personasUtils.js @@ -0,0 +1,65 @@ + +export function getCredentialSubjectForPersona(persona, decodedHeaderSubjectDID) { + const issuanceDate = new Date( + Math.floor(Date.now() / 1000) * 1000 + ).toISOString(); + const expiryDate = new Date( + Math.floor(Date.now() + 60 / 1000) * 1000 + ).toISOString(); + + const defaultData = { + id: decodedHeaderSubjectDID, + issuance_date: issuanceDate, + expiry_date: expiryDate, + issuing_authority: "https://aegean.gr", + age_over_18: true, + }; + + switch (persona) { + case "1": + return { + ...defaultData, + family_name: "Conti", + given_name: "Mario", + birth_date: "1988-11-12", + issuing_country: "IT", + }; + case "2": + return { + ...defaultData, + family_name: "Matkalainen", + given_name: "Hannah", + birth_date: "2005-02-07", + issuing_country: "FI", + }; + case "3": + return { + ...defaultData, + family_name: "Fischer", + given_name: "Felix", + birth_date: "1953-01-23", + issuing_country: "FI", + }; + default: + return { + ...defaultData, + family_name: "Doe", + given_name: "John", + birth_date: "1990-01-01", + issuing_country: "GR", + }; + } +} + + + +export function getPersonaFromAccessToken(accessToken, personas, accessTokens) { + let persona = null; + for (let i = 0; i < accessTokens.length; i++) { + console.log(accessTokens[i]); + if (accessTokens[i] === accessToken) { + persona = personas[i]; + } + } + return persona; + } \ No newline at end of file diff --git a/utils/tokenUtils.js b/utils/tokenUtils.js index 26ca835..b2f1048 100644 --- a/utils/tokenUtils.js +++ b/utils/tokenUtils.js @@ -1,6 +1,7 @@ import fs from "fs"; import jwt from "jsonwebtoken"; import crypto from "crypto"; +import { error } from "console"; export function buildAccessToken(issuerURL, privateKey) { const payload = { @@ -14,7 +15,7 @@ export function buildAccessToken(issuerURL, privateKey) { // Sign the JWT const token = jwt.sign(payload, privateKey, { algorithm: "ES256" }); -// console.log(token); + // console.log(token); return token; } @@ -41,6 +42,64 @@ export function buildIdToken(issuerURL, privateKey) { // You can add a "kid" (key ID) here if your private key has one }); -// console.log("Generated ID Token:", idToken); + // console.log("Generated ID Token:", idToken); return idToken; } + +export function buildVPbyValue( + client_id, + presentation_definition_uri, + client_id_scheme = "redirect_uri", + client_metadata_uri, + redirect_uri +) { + if (client_id_scheme == "redirect_uri") { + redirect_uri = client_id; + } + + let result = + "openid4vp://?client_id=" + + encodeURIComponent(client_id) + + "&response_type=vp_token" + + "&response_mode=direct_post" + + "&response_uri=" + + encodeURIComponent(redirect_uri) + + "&presentation_definition_uri=" + + encodeURIComponent(presentation_definition_uri) + + "&client_id_scheme=" + + client_id_scheme + + "&client_metadata_uri=" + + encodeURIComponent(client_metadata_uri) + + "&nonce=n0S6_WzA2Mj" + + "&state=af0ifjsldkj"; + return result; +} + +export function buildVPbyReference( + client_id, + presentation_definition_uri, + client_id_scheme = "redirect_uri", + client_metadata_uri, + redirect_uri +) { + if (client_id_scheme == "redirect_uri") { + throw new Error("redirect_uri is not supportted for VP by reference"); + } else { + let result = + "openid4vp://?client_id=" + + encodeURIComponent(client_id) + + "&response_type=vp_token" + + "&response_mode=direct_post" + + "&response_uri=" + + encodeURIComponent(redirect_uri) + + "&presentation_definition_uri=" + + encodeURIComponent(presentation_definition_uri) + + "&client_id_scheme=" + + client_id_scheme + + "&client_metadata_uri=" + + encodeURIComponent(client_metadata_uri) + + "&nonce=n0S6_WzA2Mj" + + "&state=af0ifjsldkj"; + return result; + } +} diff --git a/x509/client_cert.cnf b/x509/client_cert.cnf new file mode 100644 index 0000000..fd77141 --- /dev/null +++ b/x509/client_cert.cnf @@ -0,0 +1,21 @@ +[ req ] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = dn +req_extensions = req_ext + +[ dn ] +C = EU +ST = GREECE +L = GREECE +O = UAegean +OU = IT Department +CN = dss.aegean.gr + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = dss.aegean.gr +DNS.2 = www.dss.aegean.gr diff --git a/x509/client_certificate.crt b/x509/client_certificate.crt new file mode 100644 index 0000000..b4c9645 --- /dev/null +++ b/x509/client_certificate.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDvjCCAqagAwIBAgIUcw2rW9UgjWB9tCYy6PGb8uF3GsUwDQYJKoZIhvcNAQEL +BQAwcTELMAkGA1UEBhMCRVUxDzANBgNVBAgMBkdSRUVDRTEPMA0GA1UEBwwGR1JF +RUNFMRAwDgYDVQQKDAdVQWVnZWFuMRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50MRYw +FAYDVQQDDA1kc3MuYWVnZWFuLmdyMB4XDTI0MTAxNjEyMjYzNloXDTI1MTAxNjEy +MjYzNlowcTELMAkGA1UEBhMCRVUxDzANBgNVBAgMBkdSRUVDRTEPMA0GA1UEBwwG +R1JFRUNFMRAwDgYDVQQKDAdVQWVnZWFuMRYwFAYDVQQLDA1JVCBEZXBhcnRtZW50 +MRYwFAYDVQQDDA1kc3MuYWVnZWFuLmdyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEA8YrCAyyvq6vRgfScia6vs9XBoPQDUerjcMvyXUELnPC3t8ytctAP +qRpEEuLJA0+Djn5VIeSeEdtXgCkHvDj9pQNqqm5JPYPGsb1UyAKNdFOazKYK/t0w +P8pB7PTOst66HYjrwM/ETyb6hVebvdKzNYKbqm8VI/oZQtBwqxGiYj7729DvBwvs +q+mKHtIPbhZJnkgqE3nIM9PxC6eDajb2lwPQqs3uo+uOZiqJ9H08XzNAGO2wJUBu +WMtajSTBR54sbEuM8RSP5vf/+4bip6RgtQWWR4m8TnvY1b7ml0tIaUQmCQwoL6xQ +nmkJ9//jxxoxQhdwyQKdplofjbdUgQB3JQIDAQABo04wTDArBgNVHREEJDAigg1k +c3MuYWVnZWFuLmdyghF3d3cuZHNzLmFlZ2Vhbi5ncjAdBgNVHQ4EFgQUT085TK8k +rLZUVcL4SH2l6LEwg3wwDQYJKoZIhvcNAQELBQADggEBAB3Os4XxaxdG8Q2eAccp +KoDjICjRxgTQ6UFkSB21GOF5S/CtHKOcMhYtWDOd4GeQY5LrCidqcT7Oxq0qfWwy +8jqHcGSSyRqtZ4LzXyzKmm/sl0lZf0h2JrWSxkP8qcpu4vmH1JzOs1yhPPKnZGS4 +vJRVTB6JbRxFYHP5uYkQ56GguBAFQ0V0zZ0MPeduFmJL/IC2EEJTCb58BOht+kaV +HhGQPGD5Nw4hmO/Hi+WfyxJGGgRtkpIlDT7nkPIT+rLolcQHYP63qGUl6QmqILPo +1Wf3Wf7ajsBCfYm2EKqCpdSSwrtLX/qJ6RzKyubpSUx2KLbqy7fulkA6uT71QuSm +IXI= +-----END CERTIFICATE----- diff --git a/x509/client_csr.csr b/x509/client_csr.csr new file mode 100644 index 0000000..c7a2155 --- /dev/null +++ b/x509/client_csr.csr @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC9DCCAdwCAQAwcTELMAkGA1UEBhMCRVUxDzANBgNVBAgMBkdSRUVDRTEPMA0G +A1UEBwwGR1JFRUNFMRAwDgYDVQQKDAdVQWVnZWFuMRYwFAYDVQQLDA1JVCBEZXBh +cnRtZW50MRYwFAYDVQQDDA1kc3MuYWVnZWFuLmdyMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA8YrCAyyvq6vRgfScia6vs9XBoPQDUerjcMvyXUELnPC3 +t8ytctAPqRpEEuLJA0+Djn5VIeSeEdtXgCkHvDj9pQNqqm5JPYPGsb1UyAKNdFOa +zKYK/t0wP8pB7PTOst66HYjrwM/ETyb6hVebvdKzNYKbqm8VI/oZQtBwqxGiYj77 +29DvBwvsq+mKHtIPbhZJnkgqE3nIM9PxC6eDajb2lwPQqs3uo+uOZiqJ9H08XzNA +GO2wJUBuWMtajSTBR54sbEuM8RSP5vf/+4bip6RgtQWWR4m8TnvY1b7ml0tIaUQm +CQwoL6xQnmkJ9//jxxoxQhdwyQKdplofjbdUgQB3JQIDAQABoD4wPAYJKoZIhvcN +AQkOMS8wLTArBgNVHREEJDAigg1kc3MuYWVnZWFuLmdyghF3d3cuZHNzLmFlZ2Vh +bi5ncjANBgkqhkiG9w0BAQsFAAOCAQEA4IIR0C9mx7BVxBpsHQ92QVp0qBWg7Qat +SjNMHmy8/RT+kDg/mPA1si2lq0wR8q9MEar9wIlafm4O37XTvs+3bC8HRFnmbNc7 +pj6OEQw3wL5GSFD3CX3yUTOnbgT/3HYjCc6QhIW6ApUCwowi5QoMZ6GNaIKp6603 +iRCEceNi0wTTm/C4HBW+zIvyyw37DtS8cANITo7069gtGm5veHzG1i07FvAdIrb8 +3qJeyZOlqF0cVROpriba2sBPwqsivfEDd3thE8EiQofbc5qA20+DesbcJV2vjK02 ++pr9RhPq75h8G+JraHlYAxyhxur6dMxtYDYx8CQtqmce5JS+RzFzWw== +-----END CERTIFICATE REQUEST----- diff --git a/x509/client_private.key b/x509/client_private.key new file mode 100644 index 0000000..89442f0 --- /dev/null +++ b/x509/client_private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDxisIDLK+rq9GB +9JyJrq+z1cGg9ANR6uNwy/JdQQuc8Le3zK1y0A+pGkQS4skDT4OOflUh5J4R21eA +KQe8OP2lA2qqbkk9g8axvVTIAo10U5rMpgr+3TA/ykHs9M6y3rodiOvAz8RPJvqF +V5u90rM1gpuqbxUj+hlC0HCrEaJiPvvb0O8HC+yr6Yoe0g9uFkmeSCoTecgz0/EL +p4NqNvaXA9Cqze6j645mKon0fTxfM0AY7bAlQG5Yy1qNJMFHnixsS4zxFI/m9//7 +huKnpGC1BZZHibxOe9jVvuaXS0hpRCYJDCgvrFCeaQn3/+PHGjFCF3DJAp2mWh+N +t1SBAHclAgMBAAECggEABLiinC9Ga8Oa/8BJQwOwGYLHGx9J5VMDCRRjc2EvGe02 +mhgdF85rT/HcfMYECdWHC8T6YwQ19sFjz3R92zb99ji/3/7x3Yry2kyKGLK8ExcD +QpbIRj0KkWShr0EFvUdPOf6QJR+qhaWMZgjVWXuB1zzrUASF1EMyNTUfzqg0lVj+ +GGl7RYMgfk0FMPUDJ1xx5CDFzJxlOOwovYM1UR0skav9EVU+v96CemUM29WR01Vk +zyuzzForhi/J7CaLOYZHgUJN9qr1lo9YHn1N0kAbz6sNIoGc5uquLlddg6VvAAKz +XUKm4HjxSqCENdCe1K2h8cBvHdwNlWLgH9kK1MQ2QQKBgQD8W5H87X06NKGJIIg6 +zk24VVTbz0Hs8718UgL2YqzXl32LbQIgFgBewVa3HSH9pA+UJ1Fyn3uOoMx3+TXL +5fMCP2fcPqvDtb2Lw4qpVTn1/MDTBb3REfZFAWFPQ0AT2JaDxEKmYHnzpQzao/Bq +u7is6ToeODz1SwfUKCp/N3MulQKBgQD1Bzmc97RERDfrA9flvHD8SWrTr37Vl1ff +TryQ9kE9zg03lnbbUlA82keusUzsnVGbcZuloVqU5+VIgqdW5EQKF755i6eygi26 +Zao5z4guaVuVlMg9d1v9iunpD+hNqB6rq03GuwxwpRB6APWQXdLnZJRn49+MfZpW +Cy/4TFdSUQKBgHROy+kTl9zEtxKZUNAxlzZ29aZdTpgj+lgbAEyIS/sNUgp5kjox +ibgiSZIZtjnNbHSu7mXyxSKGT9aXoEi6UZbenEKxC78muxZ4aCcFJbKO7FwV0FdU +fPlu2meKqjS7ZRuTz9gYrPwzPMpkNoEqxcHDuEqHRukSzKfmXzfE2axFAoGAQhkL +2qMbdtrsNrKXt/qIhBYOAkKyNU4ZPz0PMw+q89A00oz9TRa8XF4Jvj9H4ABkNkmE +Uj7fn5Ga0V+lYpQ7InnbQRCCUW46hbDNJcEtHFABi61SAvrCzvd9OAi5gtFaTLbu +STDGH1D3y+GcXLBip1cDja7z7AMp8hIcg5+cYrECgYEA1N5XiP+LRCDVJbpkStjO +ZlYhmzbtxaBj/68sXQwuIyR1qce48z5RQhohUCqmtro69H4FB+22yc+ApOhrLszK +sgb8MRYdj00M2cv0KYUSfQ5HfrzDWyMw6Offzi3xbqaiOsN1XtpF2JzzP/XaWOOK +QwDvAMlNNf6PmgSBLZpw3EQ= +-----END PRIVATE KEY----- diff --git a/x509/client_private_pkcs8.key b/x509/client_private_pkcs8.key new file mode 100644 index 0000000..89442f0 --- /dev/null +++ b/x509/client_private_pkcs8.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDxisIDLK+rq9GB +9JyJrq+z1cGg9ANR6uNwy/JdQQuc8Le3zK1y0A+pGkQS4skDT4OOflUh5J4R21eA +KQe8OP2lA2qqbkk9g8axvVTIAo10U5rMpgr+3TA/ykHs9M6y3rodiOvAz8RPJvqF +V5u90rM1gpuqbxUj+hlC0HCrEaJiPvvb0O8HC+yr6Yoe0g9uFkmeSCoTecgz0/EL +p4NqNvaXA9Cqze6j645mKon0fTxfM0AY7bAlQG5Yy1qNJMFHnixsS4zxFI/m9//7 +huKnpGC1BZZHibxOe9jVvuaXS0hpRCYJDCgvrFCeaQn3/+PHGjFCF3DJAp2mWh+N +t1SBAHclAgMBAAECggEABLiinC9Ga8Oa/8BJQwOwGYLHGx9J5VMDCRRjc2EvGe02 +mhgdF85rT/HcfMYECdWHC8T6YwQ19sFjz3R92zb99ji/3/7x3Yry2kyKGLK8ExcD +QpbIRj0KkWShr0EFvUdPOf6QJR+qhaWMZgjVWXuB1zzrUASF1EMyNTUfzqg0lVj+ +GGl7RYMgfk0FMPUDJ1xx5CDFzJxlOOwovYM1UR0skav9EVU+v96CemUM29WR01Vk +zyuzzForhi/J7CaLOYZHgUJN9qr1lo9YHn1N0kAbz6sNIoGc5uquLlddg6VvAAKz +XUKm4HjxSqCENdCe1K2h8cBvHdwNlWLgH9kK1MQ2QQKBgQD8W5H87X06NKGJIIg6 +zk24VVTbz0Hs8718UgL2YqzXl32LbQIgFgBewVa3HSH9pA+UJ1Fyn3uOoMx3+TXL +5fMCP2fcPqvDtb2Lw4qpVTn1/MDTBb3REfZFAWFPQ0AT2JaDxEKmYHnzpQzao/Bq +u7is6ToeODz1SwfUKCp/N3MulQKBgQD1Bzmc97RERDfrA9flvHD8SWrTr37Vl1ff +TryQ9kE9zg03lnbbUlA82keusUzsnVGbcZuloVqU5+VIgqdW5EQKF755i6eygi26 +Zao5z4guaVuVlMg9d1v9iunpD+hNqB6rq03GuwxwpRB6APWQXdLnZJRn49+MfZpW +Cy/4TFdSUQKBgHROy+kTl9zEtxKZUNAxlzZ29aZdTpgj+lgbAEyIS/sNUgp5kjox +ibgiSZIZtjnNbHSu7mXyxSKGT9aXoEi6UZbenEKxC78muxZ4aCcFJbKO7FwV0FdU +fPlu2meKqjS7ZRuTz9gYrPwzPMpkNoEqxcHDuEqHRukSzKfmXzfE2axFAoGAQhkL +2qMbdtrsNrKXt/qIhBYOAkKyNU4ZPz0PMw+q89A00oz9TRa8XF4Jvj9H4ABkNkmE +Uj7fn5Ga0V+lYpQ7InnbQRCCUW46hbDNJcEtHFABi61SAvrCzvd9OAi5gtFaTLbu +STDGH1D3y+GcXLBip1cDja7z7AMp8hIcg5+cYrECgYEA1N5XiP+LRCDVJbpkStjO +ZlYhmzbtxaBj/68sXQwuIyR1qce48z5RQhohUCqmtro69H4FB+22yc+ApOhrLszK +sgb8MRYdj00M2cv0KYUSfQ5HfrzDWyMw6Offzi3xbqaiOsN1XtpF2JzzP/XaWOOK +QwDvAMlNNf6PmgSBLZpw3EQ= +-----END PRIVATE KEY-----