Skip to content

Commit

Permalink
feat: implement verification from sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
cristianIOHK committed May 6, 2024
1 parent 1a350ac commit b3941ab
Show file tree
Hide file tree
Showing 30 changed files with 2,061 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class AnoncredsTests {
polluxMock = PolluxMock()
mediationHandlerMock = MediationHandlerMock()
// Pairing will be removed in the future
connectionManager = ConnectionManager(mercuryMock, castorMock, plutoMock, mediationHandlerMock, mutableListOf(), polluxMock)
connectionManager = ConnectionManagerImpl(mercuryMock, castorMock, plutoMock, mediationHandlerMock, mutableListOf())
json = Json {
ignoreUnknownKeys = true
prettyPrint = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ constructor(
private val logger: PrismLogger = PrismLoggerImpl(LogComponent.CASTOR)
) : Castor {
var resolvers: Array<DIDResolver> = arrayOf(
PeerDIDResolver(),
LongFormPrismDIDResolver(this.apollo)
LongFormPrismDIDResolver(this.apollo),
PeerDIDResolver()
)

fun addResolver(resolver: DIDResolver) {
resolvers = resolvers.plus(resolver)
}

/**
* Parses a string representation of a Decentralized Identifier (DID) into a DID object.
*
Expand Down Expand Up @@ -108,8 +112,15 @@ constructor(
)
)
)
val resolver = CastorShared.getDIDResolver(did, resolvers)
return resolver.resolve(did)
val resolvers = CastorShared.getDIDResolver(did, resolvers)
resolvers.forEach { resolver ->
try {
val resolved = resolver.resolve(did)
return resolved
} catch (_: CastorError) {
}
}
throw Exception("No resolver could resolve the provided DID.")
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package io.iohk.atala.prism.walletsdk.castor.resolvers

import io.iohk.atala.prism.walletsdk.castor.PRISM
import io.iohk.atala.prism.walletsdk.castor.did.DIDUrlParser
import io.iohk.atala.prism.walletsdk.domain.buildingblocks.Apollo
import io.iohk.atala.prism.walletsdk.domain.models.Api
import io.iohk.atala.prism.walletsdk.domain.models.ApiImpl
import io.iohk.atala.prism.walletsdk.domain.models.CastorError
import io.iohk.atala.prism.walletsdk.domain.models.DID
import io.iohk.atala.prism.walletsdk.domain.models.DIDDocument
import io.iohk.atala.prism.walletsdk.domain.models.DIDDocumentCoreProperty
import io.iohk.atala.prism.walletsdk.domain.models.DIDResolver
import io.iohk.atala.prism.walletsdk.domain.models.httpClient
import io.iohk.atala.prism.walletsdk.prismagent.shared.KeyValue
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.didcommx.didcomm.common.Typ

class PrismDIDApiResolver(
private val apollo: Apollo,
private val cloudAgentUrl: String,
private val api: Api? = ApiImpl(
httpClient {
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
}
)
}
}
)
) : DIDResolver {
override val method: String = PRISM

override suspend fun resolve(didString: String): DIDDocument {
val response = api!!.request(
HttpMethod.Get.value,
"${this.cloudAgentUrl}/dids/$didString",
emptyArray(),
arrayOf(KeyValue(HttpHeaders.ContentType, Typ.Encrypted.typ), KeyValue(HttpHeaders.Accept, "*/*")),
null
)

if (response.status != 200) {
throw CastorError.NotPossibleToResolveDID(did = didString, reason = response.jsonString)
}

val body = Json.parseToJsonElement(response.jsonString)
val didDocJson = body.jsonObject["didDocument"].toString()

val didDocument = didDocumentFromJson(didDocJson)

return didDocument
}
}

private fun didDocumentFromJson(jsonString: String): DIDDocument {
val jsonObject = Json.parseToJsonElement(jsonString).jsonObject
val id = if (jsonObject.containsKey("id") && jsonObject["id"] != null && jsonObject["id"] is JsonPrimitive) {
DID(jsonObject["id"]!!.jsonPrimitive.content)
} else {
throw CastorError.CouldNotParseJsonIntoDIDDocument("id")
}

val coreProperties = mutableListOf<DIDDocumentCoreProperty>()
val verificationMethods: Array<DIDDocument.VerificationMethod> = getVerificationMethods(jsonObject)

// Authentications
val authenticationDidUrls = getDIDUrlsByName(jsonObject, "authentication")
val authenticationVerificationMethods =
getVerificationMethodsFromDIDUrls(authenticationDidUrls, verificationMethods)

if (authenticationDidUrls.isNotEmpty() && authenticationVerificationMethods.isNotEmpty()) {
val authentication = DIDDocument.Authentication(
urls = authenticationDidUrls,
verificationMethods = authenticationVerificationMethods
)
coreProperties.add(authentication)
}

// Assertion methods
val assertionMethodDidUrls = getDIDUrlsByName(jsonObject, "assertionMethod")
val assertionMethodVerificationMethods =
getVerificationMethodsFromDIDUrls(assertionMethodDidUrls, verificationMethods)

if (assertionMethodDidUrls.isNotEmpty() && assertionMethodVerificationMethods.isNotEmpty()) {
val assertionMethod = DIDDocument.AssertionMethod(
urls = assertionMethodDidUrls,
verificationMethods = assertionMethodVerificationMethods
)
coreProperties.add(assertionMethod)
}

// Key agreement
val keyAgreementDidUrls = getDIDUrlsByName(jsonObject, "keyAgreement")
val keyAgreementVerificationMethods = getVerificationMethodsFromDIDUrls(keyAgreementDidUrls, verificationMethods)

if (keyAgreementDidUrls.isNotEmpty() && keyAgreementVerificationMethods.isNotEmpty()) {
val keyAgreement = DIDDocument.KeyAgreement(
urls = keyAgreementDidUrls,
verificationMethods = keyAgreementVerificationMethods
)
coreProperties.add(keyAgreement)
}

// Capability invocation
val capabilityInvocationDidUrls = getDIDUrlsByName(jsonObject, "capabilityInvocation")
val capabilityVerificationMethods =
getVerificationMethodsFromDIDUrls(capabilityInvocationDidUrls, verificationMethods)

if (capabilityInvocationDidUrls.isNotEmpty() && capabilityVerificationMethods.isNotEmpty()) {
val capabilityInvocation = DIDDocument.CapabilityInvocation(
urls = capabilityInvocationDidUrls,
verificationMethods = capabilityVerificationMethods
)
coreProperties.add(capabilityInvocation)
}

// Capability delegation
val capabilityDelegationDidUrls = getDIDUrlsByName(jsonObject, "capabilityDelegation")
val capabilityDelegationVerificationMethods =
getVerificationMethodsFromDIDUrls(capabilityDelegationDidUrls, verificationMethods)

if (capabilityDelegationDidUrls.isNotEmpty() && capabilityDelegationVerificationMethods.isNotEmpty()) {
val capabilityDelegation = DIDDocument.CapabilityDelegation(
urls = capabilityDelegationDidUrls,
verificationMethods = capabilityDelegationVerificationMethods
)
coreProperties.add(capabilityDelegation)
}

// Service
val serviceDidUrls = getDIDUrlsByName(jsonObject, "service")
val serviceVerificationMethods = getVerificationMethodsFromDIDUrls(serviceDidUrls, verificationMethods)
if (serviceDidUrls.isNotEmpty() && serviceVerificationMethods.isNotEmpty()) {
val serviceDelegation = DIDDocument.CapabilityDelegation(
urls = serviceDidUrls,
verificationMethods = serviceVerificationMethods
)
coreProperties.add(serviceDelegation)
}

return DIDDocument(id, coreProperties = coreProperties.toTypedArray())
}

private fun getVerificationMethods(jsonObject: JsonObject): Array<DIDDocument.VerificationMethod> {
return if (jsonObject.containsKey("verificationMethod")) {
val verificationMethodsArray = jsonObject["verificationMethod"]?.jsonArray
val verificationMethods: MutableList<DIDDocument.VerificationMethod> = mutableListOf()
verificationMethodsArray?.forEach {
val verificationMethod = it.jsonObject
val publicKeyJwk = verificationMethod["publicKeyJwk"]?.jsonObject?.let { jwk ->
val jwkMap = mutableMapOf<String, String>()
jwk["crv"]?.jsonPrimitive?.content?.let { crv ->
jwkMap["crv"] = crv
}
jwk["x"]?.jsonPrimitive?.content?.let { x ->
jwkMap["x"] = x
}
jwk["y"]?.jsonPrimitive?.content?.let { y ->
jwkMap["y"] = y
}
jwk["kty"]?.jsonPrimitive?.content?.let { kty ->
jwkMap["kty"] = kty
}
jwkMap
}
val didId = verificationMethod["id"]?.jsonPrimitive?.content ?: throw CastorError.NullOrMissingRequiredField("id", "verificationMethod")
val controller = verificationMethod["controller"]?.jsonPrimitive?.content ?: throw CastorError.NullOrMissingRequiredField("controller", "verificationMethod")
val type = verificationMethod["type"]?.jsonPrimitive?.content ?: throw CastorError.NullOrMissingRequiredField("type", "verificationMethod")
val method = DIDDocument.VerificationMethod(
id = DIDUrlParser.parse(didId),
controller = DID(controller),
type = type,
publicKeyJwk = publicKeyJwk,
publicKeyMultibase = null
)
verificationMethods.add(method)
}
verificationMethods.toTypedArray()
} else {
emptyArray<DIDDocument.VerificationMethod>()
}
}

private fun getDIDUrlsByName(jsonObject: JsonObject, name: String): Array<String> {
return if (jsonObject.containsKey(name) &&
jsonObject[name]?.jsonArray?.isNotEmpty() == true
) {
jsonObject[name]!!.jsonArray.map {
it.jsonPrimitive.content
}.toTypedArray()
} else {
emptyArray<String>()
}
}

private fun getVerificationMethodsFromDIDUrls(
didUrls: Array<String>,
verificationMethods: Array<DIDDocument.VerificationMethod>
): Array<DIDDocument.VerificationMethod> {
val vm: MutableList<DIDDocument.VerificationMethod> = mutableListOf()
didUrls.forEach { didUrl ->
verificationMethods.forEach {
if (it.id.toString() == didUrl) {
vm.add(it)
}
}
}
return vm.toTypedArray()
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import io.iohk.atala.prism.walletsdk.logger.LogComponent
import io.iohk.atala.prism.walletsdk.logger.LogLevel
import io.iohk.atala.prism.walletsdk.logger.Metadata
import io.iohk.atala.prism.walletsdk.logger.PrismLoggerImpl
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.kotlincrypto.hash.sha2.SHA256
Expand Down Expand Up @@ -431,12 +430,9 @@ internal class CastorShared {
*/
@JvmStatic
@Throws(CastorError.NotPossibleToResolveDID::class)
internal fun getDIDResolver(did: String, resolvers: Array<DIDResolver>): DIDResolver {
internal fun getDIDResolver(did: String, resolvers: Array<DIDResolver>): List<DIDResolver> {
val parsedDID = parseDID(did)
return resolvers.find { it.method == parsedDID.method } ?: throw CastorError.NotPossibleToResolveDID(
did,
"Method or method id are invalid"
)
return resolvers.filter { it.method == parsedDID.method }
}

/**
Expand All @@ -450,25 +446,58 @@ internal class CastorShared {
return coreProperties
.filterIsInstance<DIDDocument.Authentication>()
.flatMap { it.verificationMethods.toList() }
.mapNotNull {
it.publicKeyMultibase?.let { publicKey ->
when (DIDDocument.VerificationMethod.getCurveByType(it.type)) {
Curve.SECP256K1 -> {
Secp256k1PublicKey(publicKey.encodeToByteArray())
}

Curve.ED25519 -> {
Ed25519PublicKey(publicKey.encodeToByteArray())
}

Curve.X25519 -> {
X25519PublicKey(publicKey.encodeToByteArray())
}
.mapNotNull { verificationMethod ->
when {
verificationMethod.publicKeyJwk != null -> {
extractPublicKeyFromJwk(verificationMethod.publicKeyJwk)
}

verificationMethod.publicKeyMultibase != null -> {
extractPublicKeyFromMultibase(verificationMethod.publicKeyMultibase, verificationMethod.type)
}

else -> null
}
}
}

private fun extractPublicKeyFromJwk(jwk: Map<String, String>): PublicKey? {
if (jwk.containsKey("x") && jwk.containsKey("crv")) {
val x = jwk["x"]
val crv = jwk["crv"]
return when (DIDDocument.VerificationMethod.getCurveByType(crv!!)) {
Curve.SECP256K1 -> {
Secp256k1PublicKey(x!!.encodeToByteArray())
}

Curve.ED25519 -> {
Ed25519PublicKey(x!!.encodeToByteArray())
}

Curve.X25519 -> {
X25519PublicKey(x!!.encodeToByteArray())
}
}
}
return null
}

private fun extractPublicKeyFromMultibase(publicKey: String, type: String): PublicKey {
return when (DIDDocument.VerificationMethod.getCurveByType(type)) {
Curve.SECP256K1 -> {
Secp256k1PublicKey(publicKey.encodeToByteArray())
}

Curve.ED25519 -> {
Ed25519PublicKey(publicKey.encodeToByteArray())
}

Curve.X25519 -> {
X25519PublicKey(publicKey.encodeToByteArray())
}
}
}

/**
* Create [OctetPublicKey] from a [KeyPair].
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,16 @@ interface Pluto {
*/
fun getMessage(id: String): Flow<Message?>

/**
* Retrieves the message with the specified thid.
*
* @param thid The unique ID of a request.
* @param piuri The type of message.
* @return A [Flow] that emits the message with the specified ID, or null if no such message exists.
* The [Flow] completes when the message is successfully retrieved, or when an error occurs.
*/
fun getMessageByThidAndPiuri(thid: String, piuri: String): Flow<Message?>

/**
* Returns a Flow of lists of [Mediator] objects representing all the available mediators.
*
Expand Down

0 comments on commit b3941ab

Please sign in to comment.