Skip to content

Commit

Permalink
Added DID Web Registry
Browse files Browse the repository at this point in the history
  • Loading branch information
philpotisk committed Feb 25, 2024
1 parent 4c2f4b0 commit e69e763
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 71 deletions.
11 changes: 9 additions & 2 deletions waltid-wallet-api/src/main/kotlin/id/walt/webwallet/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ fun main(args: Array<String>) {

val webConfig = ConfigManager.getConfig<WebConfig>()
log.info { "Starting web server (binding to ${webConfig.webHost}, listening on port ${webConfig.webPort})..." }
embeddedServer(CIO, port = webConfig.webPort, host = webConfig.webHost, module = Application::module)
.start(wait = true)
embeddedServer(
CIO,
port = webConfig.webPort,
host = webConfig.webHost,
module = Application::module
).start(wait = true)
}

fun Application.configurePlugins() {
Expand Down Expand Up @@ -75,4 +79,7 @@ fun Application.module() {
reports()
settings()
reasons()

// DID Web Registry
didRegistry()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package id.walt.webwallet.service

import id.walt.webwallet.db.models.WalletDid
import id.walt.webwallet.db.models.WalletDids
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction

object DidWebRegistryService {

fun listRegisteredDids(): List<String> {
return transaction {
WalletDids.select {
WalletDids.did like "did:web:%"
}.map { WalletDid(it).did }
}
}

fun loadRegisteredDid(id: String): JsonObject {
return transaction {
Json.parseToJsonElement(
WalletDids.select {
WalletDids.did like "%:$id"
}.single().let {
WalletDid(it).document
}).jsonObject
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
import java.net.URLDecoder
import kotlin.time.Duration.Companion.seconds

class SSIKit2WalletService(
tenant: String,
accountId: UUID,
Expand All @@ -91,7 +92,6 @@ class SSIKit2WalletService(
companion object {
init {
runBlocking {
//WaltidServices.init()
DidService.apply {
registerResolver(LocalResolver())
updateResolversForMethods()
Expand Down Expand Up @@ -163,11 +163,9 @@ class SSIKit2WalletService(
?: throw IllegalArgumentException("No filter pattern in presentation definition constraint")

TypeFilter(path, filterType, filterPattern)
}?.plus(
inputDescriptor.schema?.map { schema ->
}?.plus(inputDescriptor.schema?.map { schema ->
TypeFilter("type", "string", schema.uri)
} ?: listOf()
)
} ?: listOf())
}

logger.debug("Using filters: {}", filters)
Expand Down Expand Up @@ -202,8 +200,7 @@ class SSIKit2WalletService(
val params: MutableMap<String, MutableList<String>> = HashMap()
val urlParts = url.split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()

if (urlParts.size <= 1)
return params
if (urlParts.size <= 1) return params

val query = urlParts[1]
for (param in query.split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
Expand Down Expand Up @@ -237,10 +234,7 @@ class SSIKit2WalletService(

@Serializable
data class SIOPv2Response(
val vp_token: String,
val presentation_submission: String,
val id_token: String?,
val state: String?
val vp_token: String, val presentation_submission: String, val id_token: String?, val state: String?
)

private val ktorClient = HttpClient(CIO) {
Expand All @@ -252,8 +246,7 @@ class SSIKit2WalletService(


data class PresentationError(
override val message: String,
val redirectUri: String?
override val message: String, val redirectUri: String?
) : IllegalArgumentException(message)


Expand All @@ -263,7 +256,8 @@ class SSIKit2WalletService(
override suspend fun usePresentationRequest(parameter: PresentationRequestParameter): Result<String?> {
val credentialWallet = getCredentialWallet(parameter.did)

val authReq = AuthorizationRequest.fromHttpParametersAuto(parseQueryString(Url(parameter.request).encodedQuery).toMap())
val authReq =
AuthorizationRequest.fromHttpParametersAuto(parseQueryString(Url(parameter.request).encodedQuery).toMap())
logger.debug("Auth req: {}", authReq)

logger.debug("USING PRESENTATION REQUEST, SELECTED CREDENTIALS: {}", parameter.selectedCredentials)
Expand All @@ -282,24 +276,20 @@ class SSIKit2WalletService(
logger.debug("Resolved presentation definition: ${presentationSession.authorizationRequest!!.presentationDefinition!!.toJSONString()}")

val tokenResponse = credentialWallet.processImplicitFlowAuthorization(presentationSession.authorizationRequest)
val resp = ktorClient.submitForm(
presentationSession.authorizationRequest.responseUri ?: presentationSession.authorizationRequest.redirectUri
?: throw AuthorizationError(
val resp = ktorClient.submitForm(presentationSession.authorizationRequest.responseUri
?: presentationSession.authorizationRequest.redirectUri ?: throw AuthorizationError(
presentationSession.authorizationRequest,
AuthorizationErrorCode.invalid_request,
"No response_uri or redirect_uri found on authorization request"
),
parameters {
tokenResponse.toHttpParameters().forEach { entry ->
entry.value.forEach { append(entry.key, it) }
}
})
val httpResponseBody = runCatching { resp.bodyAsText() }.getOrNull()
val isResponseRedirectUrl =
httpResponseBody != null && httpResponseBody.take(10).lowercase().let {
@Suppress("HttpUrlsUsage")
it.startsWith("http://") || it.startsWith("https://")
), parameters {
tokenResponse.toHttpParameters().forEach { entry ->
entry.value.forEach { append(entry.key, it) }
}
})
val httpResponseBody = runCatching { resp.bodyAsText() }.getOrNull()
val isResponseRedirectUrl = httpResponseBody != null && httpResponseBody.take(10).lowercase().let {
@Suppress("HttpUrlsUsage") it.startsWith("http://") || it.startsWith("https://")
}
logger.debug("HTTP Response: {}, body: {}", resp, httpResponseBody)
parameter.selectedCredentials.forEach {
CredentialsService.get(walletId, it)?.run {
Expand All @@ -319,8 +309,7 @@ class SSIKit2WalletService(
if (isResponseRedirectUrl) {
Result.failure(
PresentationError(
message = "Presentation failed - redirecting to error page",
redirectUri = httpResponseBody
message = "Presentation failed - redirecting to error page", redirectUri = httpResponseBody
)
)
} else {
Expand All @@ -347,9 +336,7 @@ class SSIKit2WalletService(

private fun getCredentialWallet(did: String) = credentialWallets.getOrPut(did) {
TestCredentialWallet(
CredentialWalletConfig("http://blank"),
this,
did
CredentialWalletConfig("http://blank"), this, did
)
}

Expand All @@ -370,12 +357,10 @@ class SSIKit2WalletService(
}

private suspend fun processCredentialOffer(
credentialOffer: CredentialOffer,
credentialWallet: TestCredentialWallet
credentialOffer: CredentialOffer, credentialWallet: TestCredentialWallet
): List<CredentialResponse> {
logger.debug("// get issuer metadata")
val providerMetadataUri =
credentialWallet.getCIProviderMetadataUrl(credentialOffer.credentialIssuer)
val providerMetadataUri = credentialWallet.getCIProviderMetadataUrl(credentialOffer.credentialIssuer)
logger.debug("Getting provider metadata from: $providerMetadataUri")
val providerMetadataResult = ktorClient.get(providerMetadataUri)
logger.debug("Provider metadata returned: " + providerMetadataResult.bodyAsText())
Expand Down Expand Up @@ -418,11 +403,8 @@ class SSIKit2WalletService(
logger.debug("Using issuer URL: ${credentialOffer.credentialIssuer}")
val credReqs = offeredCredentials.map { offeredCredential ->
CredentialRequest.forOfferedCredential(
offeredCredential = offeredCredential,
proof = credentialWallet.generateDidProof(
did = credentialWallet.did,
issuerUrl = credentialOffer.credentialIssuer,
nonce = nonce
offeredCredential = offeredCredential, proof = credentialWallet.generateDidProof(
did = credentialWallet.did, issuerUrl = credentialOffer.credentialIssuer, nonce = nonce
)
)
}
Expand Down Expand Up @@ -462,19 +444,14 @@ class SSIKit2WalletService(
}

private suspend fun processMSEntraIssuanceRequest(
entraIssuanceRequest: EntraIssuanceRequest,
credentialWallet: TestCredentialWallet,
pin: String? = null
entraIssuanceRequest: EntraIssuanceRequest, credentialWallet: TestCredentialWallet, pin: String? = null
): List<CredentialResponse> {
// *) Load key:
val walletKey = getKeyByDid(credentialWallet.did)

// *) Create response JWT token, signed by key for holder DID
val responseObject = entraIssuanceRequest.getResponseObject(
walletKey.getThumbprint(),
credentialWallet.did,
walletKey.getPublicKey().exportJWK(),
pin
walletKey.getThumbprint(), credentialWallet.did, walletKey.getPublicKey().exportJWK(), pin
)
val responseToken = credentialWallet.signToken(TokenTarget.TOKEN, responseObject, keyId = credentialWallet.did)
// val jwtCryptoProvider = runBlocking {
Expand Down Expand Up @@ -553,8 +530,7 @@ class SSIKit2WalletService(
}

CredentialsService.add(
wallet = walletId,
credentials = addableCredentials.toTypedArray()
wallet = walletId, credentials = addableCredentials.toTypedArray()
)
return addableCredentials
}
Expand Down Expand Up @@ -801,10 +777,8 @@ class SSIKit2WalletService(
override suspend fun setSettings(settings: WalletSetting): Boolean = settingsService.set(walletId, settings) > 0

private fun getDidOptions(method: String, args: Map<String, JsonPrimitive>) = when (method.lowercase()) {
"key" -> DidKeyCreateOptions(
args["key"]?.let { enumValueIgnoreCase<KeyType>(it.content) } ?: KeyType.Ed25519,
args["useJwkJcsPub"]?.let { it.content.toBoolean() } ?: false
)
"key" -> DidKeyCreateOptions(args["key"]?.let { enumValueIgnoreCase<KeyType>(it.content) } ?: KeyType.Ed25519,
args["useJwkJcsPub"]?.let { it.content.toBoolean() } ?: false)

"jwk" -> DidJwkCreateOptions()
"web" -> DidWebCreateOptions(domain = args["domain"]?.content ?: "", path = args["path"]?.content ?: "")
Expand All @@ -821,19 +795,18 @@ class SSIKit2WalletService(
data: EventData,
credentialId: String? = null,
note: String? = null
) =
EventService.add(
Event(
action = action,
tenant = tenant,
originator = originator,
account = accountId,
wallet = walletId,
data = data,
credentialId = credentialId,
note = note,
)
) = EventService.add(
Event(
action = action,
tenant = tenant,
originator = originator,
account = accountId,
wallet = walletId,
data = data,
credentialId = credentialId,
note = note,
)
)

//TODO: move to related entity
private fun createCredentialEventData(credential: WalletCredential, type: String?) = CredentialEventData(
Expand Down Expand Up @@ -935,7 +908,8 @@ class SSIKit2WalletService(
private suspend fun validateTrustedIssuer(credential: WalletCredential, isEntra: Boolean) =
isEntra.takeIf { it }?.let {
trustUseCase.status(credential, true)
}?: throw IllegalArgumentException("Silent claim for this credential type not supported.")//TrustStatus.NotFound
}
?: throw IllegalArgumentException("Silent claim for this credential type not supported.")//TrustStatus.NotFound

private data class CredentialDataResult(
val credential: WalletCredential,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ object DidCreation {
description = "Domain to use to host did:web document at"
}
queryParameter<String>("path") {
description = "Path to host the did:web document at"
description = "Path to host the did:web document at. Starting with a: \"/\""
}
}
}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package id.walt.webwallet.web.controllers

import id.walt.webwallet.service.DidWebRegistryService
import io.github.smiley4.ktorswaggerui.dsl.get
import io.github.smiley4.ktorswaggerui.dsl.route
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.JsonObject

fun Application.didRegistry() = routing {
route("did-registry", {
tags = listOf("DID Web Registry")
}) {
get({
summary = "List registered DIDs"
response {
HttpStatusCode.OK to {
description = "Array of (DID) strings"
body<List<String>>()
}
}
}) {
context.respond(runBlocking {
DidWebRegistryService.listRegisteredDids()
})
}

route("{id}/did.json", {
tags = listOf("DID Web Registry")
request {
pathParameter<String>("id") {
description = "ID"
}
}
}) {
get({
summary = "Show a specific DID"

response {
HttpStatusCode.OK to {
description = "The DID document"
body<JsonObject>()
}
}
}) {

val id = context.parameters["id"] ?: throw IllegalArgumentException("No ID supplied")

context.respond(
DidWebRegistryService.loadRegisteredDid(id)
)
}
}
}
}

0 comments on commit e69e763

Please sign in to comment.