diff --git a/src/main/kotlin/id/walt/model/siopv2/SIOPv2Request.kt b/src/main/kotlin/id/walt/model/siopv2/SIOPv2Request.kt new file mode 100644 index 00000000..4f59949b --- /dev/null +++ b/src/main/kotlin/id/walt/model/siopv2/SIOPv2Request.kt @@ -0,0 +1,88 @@ +package id.walt.model.siopv2 + +import com.beust.klaxon.Json +import com.beust.klaxon.Klaxon +import id.walt.model.Claim +import io.javalin.http.Context +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +data class SIOPv2Request( + val response_type: String = "id_token", + val client_id: String, + val redirect_uri: String, + val scope: String = "openid", + val nonce: String, + val registration: Registration = Registration(), + @Json("exp") val expiration: Long, + @Json("iat") val issuedAt: Long, + val claims: Claims + + ) { + private fun enc(str: String): String = URLEncoder.encode(str, StandardCharsets.UTF_8) + fun toUriQueryString(): String { + return "response_type=${enc(response_type)}&client_id=${enc(client_id)}&redirect_uri=${enc(redirect_uri)}" + + "&scope=${enc(scope)}&nonce=${enc(nonce)}®istration=${enc(Klaxon().toJsonString(registration))}" + + "&exp=$expiration&iat=$issuedAt&claims=${enc(Klaxon().toJsonString(claims))}" + } + + companion object { + fun fromHttpContext(ctx: Context): SIOPv2Request { + val requiredParams = setOf("client_id", "redirect_uri", "nonce", "registration", "exp", "iat", "claims") + if (requiredParams.any { ctx.queryParam(it).isNullOrEmpty() }) + throw IllegalArgumentException("HTTP context missing mandatory query parameters") + return SIOPv2Request( + ctx.queryParam("response_type") ?: "id_token", + ctx.queryParam("client_id")!!, + ctx.queryParam("redirect_uri")!!, + ctx.queryParam("scope") ?: "openid", + ctx.queryParam("nonce")!!, + Klaxon().parse(ctx.queryParam("registration")!!)!!, + ctx.queryParam("exp")!!.toLong(), + ctx.queryParam("iat")!!.toLong(), + Klaxon().parse(ctx.queryParam("claims")!!)!! + ) + } + } +} + +data class Registration( + val subject_identifier_types_supported: List = listOf("did"), + val did_methods_supported: List = listOf("did:ebsi:"), + val vp_formats: VPFormats = VPFormats(), + val client_name: String? = null, + val client_purpose: String? = null, + val tos_uri: String? = null, + val logo_uri: String? = null +) + +data class VPFormats( + val jwt_vp: JwtVPFormat? = JwtVPFormat(), + val ldp_vp: LdpVpFormat? = LdpVpFormat() +) + +data class JwtVPFormat ( + val alg: Set = setOf("EdDSA", "ES256K") +) + +data class LdpVpFormat( + val proof_type: Set = setOf("Ed25519Signature2018") +) + +data class InputDescriptor ( + val id: String, + val schema: String + ) + +data class PresentationDefinition ( + val id: String, + val input_descriptors: List + ) + +data class VpTokenClaim ( + val presentation_definition: PresentationDefinition + ) + +data class Claims ( + val vp_token: VpTokenClaim? = null + ) \ No newline at end of file diff --git a/src/main/kotlin/id/walt/model/siopv2/SIOPv2Response.kt b/src/main/kotlin/id/walt/model/siopv2/SIOPv2Response.kt new file mode 100644 index 00000000..ec650ca6 --- /dev/null +++ b/src/main/kotlin/id/walt/model/siopv2/SIOPv2Response.kt @@ -0,0 +1,56 @@ +package id.walt.model.siopv2 + +import com.beust.klaxon.Json +import com.beust.klaxon.Klaxon +import id.walt.services.did.DidService +import id.walt.services.jwt.JwtService +import id.walt.services.vc.VcUtils +import id.walt.vclib.Helpers.encode +import id.walt.vclib.VcLibManager +import id.walt.vclib.vclist.VerifiablePresentation +import java.time.Instant +import java.time.temporal.Temporal +import java.util.* + +data class SIOPv2Response ( + val did: String, + val id_token: SIOPv2IDToken, + val vp_token: SIOPv2VPToken + ) { + fun getIdToken(): String { + return JwtService.getService().sign(did, Klaxon().toJsonString(id_token)) + } + + fun getVpToken(): String { + return JwtService.getService().sign(did, Klaxon().toJsonString(vp_token)) + } +} + +data class SIOPv2IDToken( + @Json("iss") val issuer: String = "https://self-issued.me/v2", + @Json("sub") val subject: String, + @Json("aud") val client_id: String, + @Json("exp") val expiration: Long = Instant.now().plusSeconds(60*60).epochSecond, + @Json("iat") val issueDate: Long = Instant.now().epochSecond, + val nonce: String) + +data class SIOPv2VPToken( + val vp_token: List +) + +data class SIOPv2Presentation( + val format: String, + val presentation: String +) { + companion object { + fun createFromVPString(vpStr: String): SIOPv2Presentation { + return SIOPv2Presentation( + format = when(VcLibManager.isJWT(vpStr)) { + true -> "jwt_vp" + else -> "ldp_vp" + }, + presentation = vpStr + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/id/walt/rest/custodian/CustodianController.kt b/src/main/kotlin/id/walt/rest/custodian/CustodianController.kt index a7c07eef..607753ba 100644 --- a/src/main/kotlin/id/walt/rest/custodian/CustodianController.kt +++ b/src/main/kotlin/id/walt/rest/custodian/CustodianController.kt @@ -111,10 +111,15 @@ object CustodianController { // ) fun listCredentialsDocs() = document() .operation { it.summary("Lists all credentials the custodian knows of").operationId("listCredentials").addTagsItem("Credentials") } + .queryParam("id", isRepeatable = true) .json("200") { it.description("Credentials list") } fun listCredentials(ctx: Context) { - ctx.json(ListCredentialsResponse(custodian.listCredentials())) + val ids = ctx.queryParams("id").toSet() + if(ids.isEmpty()) + ctx.json(ListCredentialsResponse(custodian.listCredentials())) + else + ctx.json(ListCredentialsResponse(custodian.listCredentials().filter { it.id != null && ids.contains(it.id!!)})) } // @OpenApi( diff --git a/src/main/kotlin/id/walt/services/did/DidService.kt b/src/main/kotlin/id/walt/services/did/DidService.kt index 1a3808d2..6ad858da 100644 --- a/src/main/kotlin/id/walt/services/did/DidService.kt +++ b/src/main/kotlin/id/walt/services/did/DidService.kt @@ -348,7 +348,8 @@ object DidService { KeyService.getService().delete(id!!) pubKeyJwk!!.kid = id WaltIdServices.log.debug { "Importing key: ${pubKeyJwk.kid}" } - KeyService.getService().import(Klaxon().toJsonString(pubKeyJwk)) + val keyId = KeyService.getService().import(Klaxon().toJsonString(pubKeyJwk)) + WaltContext.keyStore.addAlias(keyId, didStr) return true } else -> { diff --git a/src/main/kotlin/id/walt/signatory/CLIDataProvider.kt b/src/main/kotlin/id/walt/signatory/CLIDataProvider.kt index 8f8b2c1e..88f52d22 100644 --- a/src/main/kotlin/id/walt/signatory/CLIDataProvider.kt +++ b/src/main/kotlin/id/walt/signatory/CLIDataProvider.kt @@ -2,12 +2,14 @@ package id.walt.signatory import id.walt.vclib.model.VerifiableCredential import id.walt.vclib.vclist.VerifiableDiploma +import id.walt.vclib.vclist.VerifiableId import java.util.* object CLIDataProviders { fun getCLIDataProviderFor(templateId: String): SignatoryDataProvider? { return when(templateId) { "VerifiableDiploma" -> VerifiableDiplomaCLIDataProvider() + "VerifiableId" -> VerifiableIDCLIDataProvider() else -> null } } @@ -32,14 +34,14 @@ abstract class CLIDataProvider : SignatoryDataProvider { class VerifiableDiplomaCLIDataProvider : CLIDataProvider() { override fun populate(template: VerifiableCredential, proofConfig: ProofConfig): VerifiableCredential { template as VerifiableDiploma - + template.apply { id = proofConfig.credentialId ?: "education#higherEducation#${UUID.randomUUID()}" issuer = proofConfig.issuerDid if (proofConfig.issueDate != null) issuanceDate = dateFormat.format(proofConfig.issueDate) if (proofConfig.expirationDate != null) expirationDate = dateFormat.format(proofConfig.expirationDate) - validFrom = issuanceDate - + validFrom = issuanceDate + credentialSubject!!.apply { id = proofConfig.subjectDid @@ -67,10 +69,10 @@ class VerifiableDiplomaCLIDataProvider : CLIDataProvider() { id = proofConfig.issuerDid preferredName = prompt("Preferred name", preferredName) ?: "" homepage = prompt("Homepage", homepage) ?: "" - registration = prompt("Registration", registration) ?: "" + registration = prompt("Registration", registration) ?: "" } } - + println() println("Grading scheme") println("----------------------") @@ -101,7 +103,31 @@ class VerifiableDiplomaCLIDataProvider : CLIDataProvider() { } } } - + return template } } + +class VerifiableIDCLIDataProvider : CLIDataProvider() { + override fun populate(vc: VerifiableCredential, proofConfig: ProofConfig): VerifiableCredential { + vc as VerifiableId + vc.id = proofConfig.credentialId ?: "education#higherEducation#${UUID.randomUUID()}" + vc.issuer = proofConfig.issuerDid + if (proofConfig.issueDate != null) vc.issuanceDate = dateFormat.format(proofConfig.issueDate) + if (proofConfig.expirationDate != null) vc.expirationDate = dateFormat.format(proofConfig.expirationDate) + vc.validFrom = vc.issuanceDate + vc.credentialSubject!!.id = proofConfig.subjectDid + + println() + println("Subject personal data, ID: ${proofConfig.subjectDid}") + println("----------------------") + vc.credentialSubject!!.firstName = prompt("First name", vc.credentialSubject!!.firstName) + vc.credentialSubject!!.familyName = prompt("Family name", vc.credentialSubject!!.familyName) + vc.credentialSubject!!.dateOfBirth = prompt("Date of birth", vc.credentialSubject!!.dateOfBirth) + vc.credentialSubject!!.gender = prompt("Gender", vc.credentialSubject!!.gender) + vc.credentialSubject!!.placeOfBirth = prompt("Place of birth", vc.credentialSubject!!.placeOfBirth) + vc.credentialSubject!!.currentAddress = prompt("Current address", vc.credentialSubject!!.currentAddress) + + return vc + } +}