Skip to content

Commit

Permalink
test: did-cheqd-resolver (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeplotean authored Nov 23, 2023
1 parent d097ca0 commit f2a0aec
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 74 deletions.
10 changes: 5 additions & 5 deletions waltid-crypto/src/commonTest/kotlin/TSEKeyTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class TSEKeyTest {
@ParameterizedTest
@MethodSource
fun getPublicKey(key: TSEKey?) = runTest {
assumeTrue(hostCondition())
assumeTrue(isVaultAvailable())
assumeTrue(key != null)
val publicKey = key!!.getPublicKey()
assertTrue(!publicKey.hasPrivateKey)
Expand All @@ -55,7 +55,7 @@ class TSEKeyTest {
@ParameterizedTest
@MethodSource
fun signRaw(key: TSEKey?) = runTest {
assumeTrue(hostCondition())
assumeTrue(isVaultAvailable())
assumeTrue(key != null)
val signed = key!!.signRaw(payload.toString().encodeToByteArray()) as String
val verificationResult = key.verifyRaw(signed.decodeBase64Bytes(), payload.toString().encodeToByteArray())
Expand All @@ -66,7 +66,7 @@ class TSEKeyTest {
@ParameterizedTest
@MethodSource
fun signJws(key: TSEKey?) = runTest {
assumeTrue(hostCondition())
assumeTrue(isVaultAvailable())
val signed = key!!.signJws(payload.toString().encodeToByteArray())
val verificationResult = key.verifyJws(signed)
assertTrue(verificationResult.isSuccess)
Expand Down Expand Up @@ -99,7 +99,7 @@ class TSEKeyTest {
@JvmStatic
@BeforeAll
fun initKeys() = runTest {
hostCondition().takeIf { it }?.let {
isVaultAvailable().takeIf { it }?.let {
val tseMetadata = TSEKeyMetadata("http://127.0.0.1:8200/v1/transit", "dev-only-token")
keys = enumValues<KeyType>().map { TSEKey.generate(KeyType.Ed25519, tseMetadata) }
}
Expand All @@ -116,7 +116,7 @@ class TSEKeyTest {
@JvmStatic
fun signJws(): Stream<Arguments> = keys.map { arguments(it) }.asSequence().asStream()

private fun hostCondition() = runCatching {
private fun isVaultAvailable() = runCatching {
runBlocking { HttpClient().get("http://127.0.0.1:8200") }.status == HttpStatusCode.OK
}.fold(onSuccess = { it }, onFailure = { false })
}
Expand Down
4 changes: 2 additions & 2 deletions waltid-crypto/src/jvmTest/kotlin/TestJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class TestJvm {
}

@Test
@EnabledIf("hostCondition")
@EnabledIf("isVaultAvailable")
fun testKeySerialization() = runTest {
val localKey = LocalKey.generate(KeyType.Ed25519)
val localKeySerialized = KeySerialization.serializeKey(localKey)
Expand Down Expand Up @@ -189,7 +189,7 @@ class TestJvm {
}
}

private fun hostCondition() = runCatching {
private fun isVaultAvailable() = runCatching {
runBlocking { HttpClient().get("http://127.0.0.1:8200") }.status == HttpStatusCode.OK
}.fold(onSuccess = { true }, onFailure = { false })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,9 @@ object DidService {
getRegistrarForMethod(options.method).create(options)
}

suspend fun registerByKey(method: String, key: Key, options: DidCreateOptions = DidCreateOptions(method, emptyMap())): DidResult =
getRegistrarForMethod(method).createByKey(key, options)
suspend fun registerByKey(
method: String, key: Key, options: DidCreateOptions = DidCreateOptions(method, emptyMap())
): DidResult = getRegistrarForMethod(method).createByKey(key, options)

fun update() {
TODO("Not yet implemented")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ data class DidCheqdDocument(

fun toMap() = Json.encodeToJsonElement(this).jsonObject.toMap()

constructor(didDoc: DidDocument, jwk: JsonObject) : this(
constructor(didDoc: DidDocument, jwk: JsonObject? = null) : this(
context = DEFAULT_CONTEXT,
id = didDoc.id,
verificationMethod = didDoc.verificationMethod.map {
VerificationMethod(it.id, "JsonWebKey2020", it.controller, jwk)
//TODO: publicKeyMultibase
verificationMethod = jwk?.let { key ->
didDoc.verificationMethod.map {
VerificationMethod(it.id, "JsonWebKey2020", it.controller, key)
}
},
authentication = didDoc.authentication,
assertionMethod = didDoc.authentication,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.encodeToJsonElement

class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
class DidCheqdRegistrar : LocalRegistrarMethod("cheqd") {

private val log = KotlinLogging.logger { }

Expand Down Expand Up @@ -151,4 +151,4 @@ class DidCheqdRegistrar() : LocalRegistrarMethod("cheqd") {
java.util.Base64.getUrlEncoder().encodeToString(key.signRaw(it) as ByteArray)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DidJwkRegistrar : LocalRegistrarMethod("jwk") {
} ?: throw IllegalArgumentException("KeyType option not found.")

override suspend fun registerByKey(key: Key, options: DidCreateOptions): DidResult {
val did = "did:jwk:${key.exportJWK().toByteArray().encodeToBase64Url()}"
val did = "did:jwk:${key.getPublicKey().exportJWK().toByteArray().encodeToBase64Url()}"

val didDocument = DidDocument(
DidJwkDocument(did, key.getPublicKey().exportJWKObject())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package id.walt.did.dids.resolver.local

import id.walt.crypto.keys.Key
import id.walt.did.dids.document.DidCheqdDocument
import id.walt.did.dids.document.DidDocument
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

class DidCheqdResolver : LocalResolverMethod("cheqd") {
private val httpClient = HttpClient() //TODO: inject

override suspend fun resolve(did: String): Result<DidDocument> = runCatching {
resolveDid(did)
}

override suspend fun resolveToKey(did: String): Result<Key> {
TODO("Not yet implemented")
// response verificationMethod contains only publicKeyMultibase
// required: convert it to publicKeyJwk
// (no functionality provided by crypto, only multibase58btc available)
}

private val json = Json() { ignoreUnknownKeys = true }

private fun resolveDid(did: String): DidDocument {
val response = runBlocking {
httpClient.get("https://resolver.cheqd.net/1.0/identifiers/${did}") {
headers {
append("contentType", "application/did+ld+json")
}
}.bodyAsText()
}
val resolution = Json.decodeFromString<JsonObject>(response)

val didDocument = resolution.jsonObject["didResolutionMetadata"]?.jsonObject?.get("error")?.let {
throw IllegalArgumentException("Could not resolve did:cheqd, resolver responded: ${it.jsonPrimitive.content}")
} ?: let {
resolution.jsonObject["didDocument"]?.jsonObject
?: throw IllegalArgumentException("Response for did:cheqd did not contain a DID document!")
}.let {
json.decodeFromString<id.walt.did.dids.registrar.local.cheqd.models.job.didstates.finished.DidDocument>(
it.toString()
)
}
return DidDocument(DidCheqdDocument(didDocument).toMap())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,6 @@ import kotlinx.serialization.json.jsonObject

class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web") {

companion object {
const val URL_PROTOCOL = "https"
val json = Json { ignoreUnknownKeys = true }
}

private fun resolveDidToUrl(did: String): String = DidUtils.identifierFromDid(did)?.let {
val didParts = it.split(":")

val domain = didParts[0].replace("%3A", ":")
val selectedPath = didParts.drop(1)

val path = when {
selectedPath.isEmpty() -> "/.well-known/did.json"
else -> "/${selectedPath.joinToString("/")}/did.json"
}

"$URL_PROTOCOL://$domain$path"
} ?: throw IllegalArgumentException("Unexpected did format (missing identifier): $did")

override suspend fun resolve(did: String): Result<DidDocument> {
val url = resolveDidToUrl(did)

Expand All @@ -46,14 +27,6 @@ class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web"
return response
}

suspend fun tryConvertAnyPublicKeyJwkToKey(publicKeyJwks: List<String>): Result<LocalKey> {
publicKeyJwks.forEach { publicKeyJwk ->
val result = LocalKey.importJWK(publicKeyJwk)
if (result.isSuccess) return result
}
return Result.failure(NoSuchElementException("No key could be imported"))
}

override suspend fun resolveToKey(did: String): Result<Key> {
val didDocumentResult = resolve(did)
if (didDocumentResult.isFailure) return Result.failure(didDocumentResult.exceptionOrNull()!!)
Expand All @@ -70,4 +43,31 @@ class DidWebResolver(private val client: HttpClient) : LocalResolverMethod("web"

return tryConvertAnyPublicKeyJwkToKey(publicKeyJwks)
}

private fun resolveDidToUrl(did: String): String = DidUtils.identifierFromDid(did)?.let {
val didParts = it.split(":")

val domain = didParts[0].replace("%3A", ":")
val selectedPath = didParts.drop(1)

val path = when {
selectedPath.isEmpty() -> "/.well-known/did.json"
else -> "/${selectedPath.joinToString("/")}/did.json"
}

"$URL_PROTOCOL://$domain$path"
} ?: throw IllegalArgumentException("Unexpected did format (missing identifier): $did")

suspend fun tryConvertAnyPublicKeyJwkToKey(publicKeyJwks: List<String>): Result<LocalKey> {
publicKeyJwks.forEach { publicKeyJwk ->
val result = LocalKey.importJWK(publicKeyJwk)
if (result.isSuccess) return result
}
return Result.failure(NoSuchElementException("No key could be imported"))
}

companion object {
const val URL_PROTOCOL = "https"
val json = Json { ignoreUnknownKeys = true }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ import id.walt.crypto.keys.TSEKeyMetadata
import id.walt.did.dids.DidService
import id.walt.did.dids.registrar.dids.DidKeyCreateOptions
import id.walt.did.helpers.WaltidServices
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.time.Duration.Companion.minutes

class DidTest {
class DidExamples {

val remoteKey = false
private val tseMetadata = TSEKeyMetadata("http://127.0.0.1:8200/v1/transit", "dev-only-token")

@BeforeTest
fun init() {
Expand All @@ -28,9 +32,6 @@ class DidTest {

@Test
fun listDidMethods() {
// println("Registrar: " + DidService.registrarMethods.toList().groupBy { it.second.name })
// println("Resolver: " + DidService.resolverMethods.toList().groupBy { it.second.name })

println("Resolver:")
println(
groupDidList(DidService.resolverMethods.mapValues { it.value.name })
Expand All @@ -42,27 +43,32 @@ class DidTest {
)
}


val tseMetadata = TSEKeyMetadata("http://127.0.0.1:8200/v1/transit", "dev-only-token")

@Test
fun createDidJwk() = runTest {
fun exampleCreateDidJwk() = runTest {

val key = if (remoteKey) TSEKey.generate(KeyType.Ed25519, tseMetadata) else LocalKey.generate(KeyType.Ed25519)
val key = if (isVaultAvailable()) TSEKey.generate(
KeyType.Ed25519, tseMetadata
) else LocalKey.generate(KeyType.Ed25519)

val did = DidService.registerByKey("jwk", key)

println(did.didDocument.toJsonObject())
}

@Test
fun createDidKeyJcs() = runTest {
fun exampleCreateDidKeyJcs() = runTest {

val key = if (remoteKey) TSEKey.generate(KeyType.Ed25519, tseMetadata) else LocalKey.generate(KeyType.Ed25519)
val key = if (isVaultAvailable()) TSEKey.generate(
KeyType.Ed25519, tseMetadata
) else LocalKey.generate(KeyType.Ed25519)

val options = DidKeyCreateOptions(KeyType.Ed25519, useJwkJcsPub = true)
val did = DidService.registerByKey("key", key, options)

println(did.didDocument.toJsonObject())
}

private fun isVaultAvailable() = runCatching {
runBlocking { HttpClient().get("http://127.0.0.1:8200") }.status == HttpStatusCode.OK
}.fold(onSuccess = { it }, onFailure = { false })
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ class DidKeyRegistrarTest : DidRegistrarTestBase(DidKeyRegistrar()) {
@JvmStatic
fun `given did options with no key when register then returns a valid did result`(): Stream<Arguments> =
Stream.of(
// arguments(DidKeyCreateOptions(useJwkJcsPub = true), ed25519DidAssertions),
// arguments(DidKeyCreateOptions(KeyType.Ed25519), ed25519DidAssertions),
// arguments(DidKeyCreateOptions(KeyType.RSA), rsaDidAssertions),
// arguments(DidKeyCreateOptions(KeyType.secp256k1), secp256DidAssertions),
arguments(DidKeyCreateOptions(useJwkJcsPub = true), ed25519DidAssertions),
arguments(DidKeyCreateOptions(KeyType.Ed25519), ed25519DidAssertions),
arguments(DidKeyCreateOptions(KeyType.RSA), rsaDidAssertions),
arguments(DidKeyCreateOptions(KeyType.secp256k1), secp256DidAssertions),
arguments(DidKeyCreateOptions(KeyType.secp256r1), secp256DidAssertions),
)

Expand Down
61 changes: 61 additions & 0 deletions waltid-did/src/jvmTest/kotlin/resolvers/DidCheqdResolverTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package resolvers

import id.walt.crypto.keys.Key
import id.walt.did.dids.document.DidDocument
import id.walt.did.dids.resolver.local.DidCheqdResolver
import id.walt.did.dids.resolver.local.LocalResolverMethod
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.Arguments.arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

class DidCheqdResolverTest : DidResolverTestBase() {
override val sut: LocalResolverMethod = DidCheqdResolver()

@ParameterizedTest
@MethodSource
override fun `given a did String, when calling resolve, then the result is a valid did document`(
did: String,
key: JsonObject,
assert: resolverAssertion<DidDocument>
) {
super.`given a did String, when calling resolve, then the result is a valid did document`(did, key, assert)
}

@ParameterizedTest
@MethodSource
@Disabled // not implemented
override fun `given a did String, when calling resolveToKey, then the result is valid key`(
did: String,
key: JsonObject,
assert: resolverAssertion<Key>
) {
super.`given a did String, when calling resolveToKey, then the result is valid key`(did, key, assert)
}

companion object {
@JvmStatic
fun `given a did String, when calling resolve, then the result is a valid did document`(): Stream<Arguments> =
Stream.of(
arguments(
"did:cheqd:testnet:W5a3426DZ1f4qBkYC9ZT6s",
Json.decodeFromString<JsonObject>("{\"kty\":\"OKP\",\"use\":\"sig\",\"crv\":\"Ed25519\",\"kid\":\"151df6ec01714883b812f26f2d63e584\",\"x\":\"qBDsYw3k62mUT8UmEx99Xz3yckiSRmTsL6aa21ZcAVM\",\"alg\":\"EdDSA\"}"),
ed25519DidAssertions
)
)

@JvmStatic
fun `given a did String, when calling resolveToKey, then the result is valid key`(): Stream<Arguments> =
Stream.of(
arguments(
"did:cheqd:testnet:38088d21-a5f8-4277-bd35-b36918d81c14",
Json.decodeFromString<JsonObject>("{\"kty\":\"OKP\",\"d\":\"24WxHxiKpnd1_BitZBU57ex8EKaNukiyC4punO4Lh-s\",\"crv\":\"Ed25519\",\"kid\":\"GaQD9pzL5wJyATB1UA2J71ygXgkykT1QOnL7uIBgcpo\",\"x\":\"bz2K3xX-D_R2_Pu7al6UCRXGSl1pzBRfEoD3bj94s_w\"}"),
ed25519KeyAssertions
)
)
}
}
Loading

0 comments on commit f2a0aec

Please sign in to comment.