From 51aa7d4809e26e78f64099a074fb1c27d1b90a3d Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 22 Aug 2024 09:11:27 -0400 Subject: [PATCH 01/22] feat: verification sd-jwt Signed-off-by: Cristian G --- .../domain/models/ProvableCredential.kt | 2 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 37 +++++--- .../identus/walletsdk/pollux/PolluxImpl.kt | 10 +-- .../walletsdk/pollux/models/AnonCredential.kt | 2 +- .../walletsdk/pollux/models/JWTCredential.kt | 88 +++++++++++++++++-- .../pollux/models/SDJWTCredential.kt | 2 +- .../utils/{Bitstring.kt => BitString.kt} | 2 +- .../walletsdk/edgeagent/EdgeAgentTests.kt | 22 ++--- .../ui/messages/MessagesViewModel.kt | 67 +++++++------- 9 files changed, 160 insertions(+), 72 deletions(-) rename edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/{Bitstring.kt => BitString.kt} (98%) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt index 5f4e68bbf..bd13065bf 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt @@ -20,5 +20,5 @@ sealed class CredentialOperationsOptions { } interface ProvableCredential { - suspend fun presentation(request: ByteArray, options: List): String + suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List): String } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 9311fecfc..9e876bfaa 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -16,6 +16,7 @@ import com.nimbusds.jose.crypto.X25519Decrypter import com.nimbusds.jose.crypto.X25519Encrypter import com.nimbusds.jose.jwk.OctetKeyPair import com.nimbusds.jose.util.Base64URL +import eu.europa.ec.eudi.sdjwt.SdJwt import eu.europa.ec.eudi.sdjwt.vc.SD_JWT_VC_TYPE import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.ContentType @@ -986,6 +987,7 @@ open class EdgeAgent { val linkSecret = getLinkSecret() try { presentationString = credential.presentation( + attachmentFormat, requestData.encodeToByteArray(), listOf( CredentialOperationsOptions.LinkSecret("", linkSecret), @@ -1020,6 +1022,7 @@ open class EdgeAgent { } try { presentationString = credential.presentation( + attachmentFormat, requestData.encodeToByteArray(), listOf( CredentialOperationsOptions.SubjectDID(subjectDID), @@ -1041,6 +1044,7 @@ open class EdgeAgent { }.first().encodeToByteArray() try { presentationString = credential.presentation( + attachmentFormat, requestData, listOf(CredentialOperationsOptions.DisclosingClaims(listOf(credential.claims.toString()))) ) @@ -1152,7 +1156,10 @@ open class EdgeAgent { val presentationDefinitionRequestString = requestPresentation.attachments.firstNotNullOf { it.data.getDataAsJsonString() } - if (presentationDefinitionRequestString.contains("jwt")) { + + if (presentationDefinitionRequestString.contains("jwt") || + presentationDefinitionRequestString.contains("vc+sd-jwt") + ) { // If the json can be used to instantiate a JWTPresentationDefinitionRequest, process the request // as JWT. val didString = @@ -1162,17 +1169,22 @@ open class EdgeAgent { ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) val privateKey = apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) - val presentationSubmissionProof = pollux.createJWTPresentationSubmission( - presentationDefinitionRequest = presentationDefinitionRequestString, - credential = credential, - privateKey = privateKey, + + val presentationString = credential.presentation( + CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + presentationDefinitionRequestString.encodeToByteArray(), + listOf( + CredentialOperationsOptions.SubjectDID(DID(didString)), + CredentialOperationsOptions.ExportableKey(privateKey) + ) ) val attachmentDescriptor = AttachmentDescriptor( mediaType = "application/json", format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, - data = AttachmentBase64(presentationSubmissionProof.base64UrlEncoded) + data = AttachmentBase64(presentationString.base64UrlEncoded) ) + return Presentation( body = Presentation.Body(), attachments = arrayOf(attachmentDescriptor), @@ -1182,10 +1194,15 @@ open class EdgeAgent { ) } else { val linkSecret = getLinkSecret() - val presentationSubmissionProof = pollux.createAnoncredsPresentationSubmission( - presentationDefinitionRequest = presentationDefinitionRequestString, - credential = credential, - linkSecret = linkSecret + + val presentationSubmissionProof = credential.presentation( + CredentialType.ANONCREDS_PROOF_REQUEST.type, + presentationDefinitionRequestString.toByteArray(), + listOf( + CredentialOperationsOptions.LinkSecret("", linkSecret), + CredentialOperationsOptions.SchemaDownloader(api), + CredentialOperationsOptions.CredentialDefinitionDownloader(api) + ) ) val attachmentDescriptor = AttachmentDescriptor( diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 13aaf6887..2c159be73 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -103,7 +103,7 @@ import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.hyperledger.identus.walletsdk.pollux.models.VerificationKeyType import org.hyperledger.identus.walletsdk.pollux.models.W3CCredential -import org.hyperledger.identus.walletsdk.pollux.utils.Bitstring +import org.hyperledger.identus.walletsdk.pollux.utils.BitString /** * Class representing the implementation of the Pollux interface. @@ -557,7 +557,7 @@ open class PolluxImpl( if (encodedList != null) { val decodedBytes = Base64.getUrlDecoder().decode(encodedList) val decompressedBytes = decodedBytes.gunzip() - val bitString = Bitstring(decompressedBytes) + val bitString = BitString(decompressedBytes) if (statusListIndex > decompressedBytes.size) { throw PolluxError.StatusListOutOfBoundIndex() @@ -868,6 +868,7 @@ open class PolluxImpl( } } + // TODO: Deprecate, the new way to create JWT presentation submission is using the credential. ProvableCredential.presentation() override suspend fun createJWTPresentationSubmission( presentationDefinitionRequest: String, credential: Credential, @@ -1035,11 +1036,6 @@ open class PolluxImpl( return@let PresentationSubmission(submission, arrayStrings) } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") -// val presentationDefinitionRequest = -// (options as PresentationSubmissionOptionsJWT).presentationDefinitionRequest as JWTPresentationDefinitionRequest -// presentationDefinitionRequest.presentationDefinition -// val inputDescriptors = -// presentationDefinitionRequest.presentationDefinition.inputDescriptors val presentationDefinitionRequestString = (options as PresentationSubmissionOptionsJWT).presentationDefinitionRequest val presentationDefinitionRequest = diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/AnonCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/AnonCredential.kt index 08839e35c..aa9e1bb52 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/AnonCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/AnonCredential.kt @@ -143,7 +143,7 @@ data class AnonCredential( override var revoked: Boolean? = null - override suspend fun presentation(request: ByteArray, options: List): String { + override suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List): String { var schemaDownloader: Api? = null var definitionDownloader: Api? = null var linkSecret: String? = null diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt index 52b857ba6..4a7c476bc 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt @@ -14,6 +14,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ArraySerializer import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement @@ -29,6 +30,7 @@ import org.hyperledger.identus.walletsdk.domain.models.Claim import org.hyperledger.identus.walletsdk.domain.models.ClaimType import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.domain.models.CredentialOperationsOptions +import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.DID import org.hyperledger.identus.walletsdk.domain.models.JWTPayload import org.hyperledger.identus.walletsdk.domain.models.JWTVerifiableCredential @@ -36,6 +38,8 @@ import org.hyperledger.identus.walletsdk.domain.models.JWTVerifiablePresentation import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential import org.hyperledger.identus.walletsdk.domain.models.StorableCredential +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey import org.hyperledger.identus.walletsdk.pollux.CHALLENGE import org.hyperledger.identus.walletsdk.pollux.CONTEXT import org.hyperledger.identus.walletsdk.pollux.CONTEXT_URL @@ -46,8 +50,9 @@ import org.hyperledger.identus.walletsdk.pollux.TYPE import org.hyperledger.identus.walletsdk.pollux.VERIFIABLE_CREDENTIAL import org.hyperledger.identus.walletsdk.pollux.VERIFIABLE_PRESENTATION import org.hyperledger.identus.walletsdk.pollux.VP -import java.security.PrivateKey import java.security.interfaces.ECPrivateKey +import java.util.* +import java.security.PrivateKey as JavaPrivateKey @Serializable /** @@ -126,23 +131,92 @@ data class JWTCredential @JvmOverloads constructor( } override var revoked: Boolean? = false - override suspend fun presentation(request: ByteArray, options: List): String { - var exportableKeyOption: org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey? = null + + override suspend fun presentation( + attachmentFormat: String, + request: ByteArray, + options: List + ): String { + var exportableKey: PrivateKey? = null var subjectDID: DID? = null for (option in options) { when (option) { is CredentialOperationsOptions.SubjectDID -> subjectDID = option.did - is CredentialOperationsOptions.ExportableKey -> exportableKeyOption = option.key + is CredentialOperationsOptions.ExportableKey -> exportableKey = option.key else -> {} } } if (subjectDID == null) { throw PolluxError.InvalidPrismDID() } - if (exportableKeyOption == null) { + if (exportableKey == null) { throw PolluxError.WrongKeyProvided("Secp256k1", actual = "null") } + + return when (attachmentFormat) { + CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type -> { + presentationSubmission(request, exportableKey) + } + + else -> { + vcPresentation(request, subjectDID, exportableKey) + } + } + } + + private fun presentationSubmission(request: ByteArray, privateKey: PrivateKey): String { + val jwtPresentationDefinitionRequest = + Json.decodeFromString(String(request, Charsets.UTF_8)) + val descriptorItems = + jwtPresentationDefinitionRequest.presentationDefinition.inputDescriptors.map { inputDescriptor -> + if (inputDescriptor.format != null && (inputDescriptor.format.jwt == null || inputDescriptor.format.jwt.alg.isEmpty())) { + throw PolluxError.InvalidCredentialDefinitionError() + } + PresentationSubmission.Submission.DescriptorItem( + id = inputDescriptor.id, + format = DescriptorItemFormat.JWT_VP.value, + path = "$.verifiablePresentation[0]", + pathNested = PresentationSubmission.Submission.DescriptorItem( + id = inputDescriptor.id, + format = DescriptorItemFormat.JWT_VC.value, + path = "$.vp.verifiableCredential[0]" + ) + ) + }.toTypedArray() + + val credentialSubject = this.subject + credentialSubject?.let { subject -> + if (!privateKey.isSignable()) { + throw PolluxError.WrongKeyProvided( + expected = SignableKey::class.simpleName, + actual = privateKey::class.simpleName + ) + } + + val ecPrivateKey = privateKey.jca() as ECPrivateKey + val presentationJwt = signClaimsProofPresentationJWT( + subjectDID = DID(subject), + privateKey = ecPrivateKey, + credential = this, + domain = jwtPresentationDefinitionRequest.options.domain, + challenge = jwtPresentationDefinitionRequest.options.challenge + ) + + return Json.encodeToString( + PresentationSubmission( + presentationSubmission = PresentationSubmission.Submission( + definitionId = jwtPresentationDefinitionRequest.presentationDefinition.id + ?: UUID.randomUUID().toString(), + descriptorMap = descriptorItems + ), + verifiablePresentation = arrayOf(presentationJwt) + ) + ) + } ?: throw PolluxError.NullField("CredentialSubject") + } + + private fun vcPresentation(request: ByteArray, subjectDID: DID, exportableKey: PrivateKey): String { val jsonString = String(request, Charsets.UTF_8) val requestJson = Json.parseToJsonElement(jsonString).jsonObject val domain = @@ -151,7 +225,7 @@ data class JWTCredential @JvmOverloads constructor( getChallenge(requestJson) ?: throw PolluxError.NoDomainOrChallengeFound() return signClaimsProofPresentationJWT( subjectDID, - exportableKeyOption.jca() as ECPrivateKey, + exportableKey.jca() as ECPrivateKey, this, domain, challenge @@ -319,7 +393,7 @@ data class JWTCredential @JvmOverloads constructor( // Sign the JWT with the private key val jwsObject = SignedJWT(header, claims) val signer = ECDSASigner( - privateKey as PrivateKey, + privateKey as JavaPrivateKey, Curve.SECP256K1 ) val provider = BouncyCastleProviderSingleton.getInstance() diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index c879ed33b..90702e226 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -63,7 +63,7 @@ data class SDJWTCredential( override var revoked: Boolean? = null - override suspend fun presentation(request: ByteArray, options: List): String { + override suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List): String { var exportableKeyOption: PrivateKey? = null var disclosingClaims: List? = null diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/Bitstring.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/BitString.kt similarity index 98% rename from edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/Bitstring.kt rename to edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/BitString.kt index b115110c4..ec054c17b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/Bitstring.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/utils/BitString.kt @@ -4,7 +4,7 @@ import kotlin.experimental.and import kotlin.experimental.inv import kotlin.experimental.or -class Bitstring( +class BitString( buffer: ByteArray, private val leftToRightIndexing: Boolean = false ) { diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index f240d1501..cc4402142 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -95,6 +95,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.mock import org.mockito.kotlin.spy +import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -1340,18 +1341,11 @@ class EdgeAgentTests { ).thenReturn(privateKey) val presentationSubmissionString = - "{\"presentation_submission\":{\"id\":\"00000000-c224-45d7-0000-0000732f4932\",\"definition_id\":\"32f54163-7166-48f1-93d8-ff217bdb0653\",\"descriptor_map\":[{\"id\":\"wa_driver_license\",\"format\":\"jwt\",\"path\":\"$.verifiablePresentation[0]\"}]},\"verifiablePresentation\":[\"eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MjU3MTlhOTZiMTUxMjA3MTY5ODFhODQzMGFkMGNiOTY4ZGQ1MzQwNzM1OTNjOGNkM2YxZDI3YTY4MDRlYzUwZTpDcG9DQ3BjQ0Vsb0tCV3RsZVMweEVBSkNUd29KYzJWamNESTFObXN4RWlBRW9TQ241dHlEYTZZNnItSW1TcXBKOFkxbWo3SkMzX29VekUwTnl5RWlDQm9nc2dOYWVSZGNDUkdQbGU4MlZ2OXRKZk53bDZyZzZWY2hSM09xaGlWYlRhOFNXd29HWVhWMGFDMHhFQVJDVHdvSmMyVmpjREkxTm1zeEVpRE1rQmQ2RnRpb0prM1hPRnUtX2N5NVhtUi00dFVRMk5MR2lXOGFJU29ta1JvZzZTZGU5UHduRzBRMFNCVG1GU1REYlNLQnZJVjZDVExYcmpJSnR0ZUdJbUFTWEFvSGJXRnpkR1Z5TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTzcxMG10MVdfaXhEeVFNM3hJczdUcGpMQ05PRFF4Z1ZoeDVzaGZLTlgxb2FJSFdQcnc3SVVLbGZpYlF0eDZKazRUU2pnY1dOT2ZjT3RVOUQ5UHVaN1Q5dCIsInN1YiI6ImRpZDpwcmlzbTpiZWVhNTIzNGFmNDY4MDQ3MTRkOGVhOGVjNzdiNjZjYzdmM2U4MTVjNjhhYmI0NzVmMjU0Y2Y5YzMwNjI2NzYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklnZVNnLTJPTzFKZG5welVPQml0eklpY1hkZnplQWNUZldBTi1ZQ2V1Q2J5SWFJSlE0R1RJMzB0YVZpd2NoVDNlMG5MWEJTNDNCNGo5amxzbEtvMlpsZFh6akVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lIa29QdGpqdFNYWjZjMURnWXJjeUluRjNYODNnSEUzMWdEZm1BbnJnbThpR2lDVU9Ca3lOOUxXbFlzSElVOTN0Snkxd1V1TndlSV9ZNWJKU3FObVpYVjg0dyIsIm5iZiI6MTY4NTYzMTk5NSwiZXhwIjoxNjg1NjM1NTk1LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsUHJvcDIiOiJUZXN0MyIsImlkIjoiZGlkOnByaXNtOmJlZWE1MjM0YWY0NjgwNDcxNGQ4ZWE4ZWM3N2I2NmNjN2YzZTgxNWM2OGFiYjQ3NWYyNTRjZjljMzA2MjY3NjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdlU2ctMk9PMUpkbnB6VU9CaXR6SWljWGRmemVBY1RmV0FOLVlDZXVDYnlJYUlKUTRHVEkzMHRhVml3Y2hUM2UwbkxYQlM0M0I0ajlqbHNsS28yWmxkWHpqRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUhrb1B0amp0U1haNmMxRGdZcmN5SW5GM1g4M2dIRTMxZ0RmbUFucmdtOGlHaUNVT0JreU45TFdsWXNISVU5M3RKeTF3VXVOd2VJX1k1YkpTcU5tWlhWODR3In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.x0SF17Y0VCDmt7HceOdTxfHlofsZmY18Rn6VQb0-r-k_Bm3hTi1-k2vkdjB25hdxyTCvxam-AkAP-Ag3Ahn5Ng\"]}" - val presentationSubmission = presentationSubmissionString + """{"presentation_submission":{"id":"2ada28b3-7866-4769-8c0a-31de12c51ee7","definition_id":"32f54163-7166-48f1-93d8-ff217bdb0653","descriptor_map":[{"id":"wa_driver_license","format":"jwt_vp","path":"${'$'}.verifiablePresentation[0]","path_nested":{"id":"wa_driver_license","format":"jwt_vc","path":"${'$'}.vp.verifiableCredential[0]"}}]},"verifiablePresentation":["eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206YmVlYTUyMzRhZjQ2ODA0NzE0ZDhlYThlYzc3YjY2Y2M3ZjNlODE1YzY4YWJiNDc1ZjI1NGNmOWMzMDYyNjc2MzpDc2NCQ3NRQkVtUUtEMkYxZEdobGJuUnBZMkYwYVc5dU1CQUVRazhLQ1hObFkzQXlOVFpyTVJJZ2VTZy0yT08xSmRucHpVT0JpdHpJaWNYZGZ6ZUFjVGZXQU4tWUNldUNieUlhSUpRNEdUSTMwdGFWaXdjaFQzZTBuTFhCUzQzQjRqOWpsc2xLbzJabGRYempFbHdLQjIxaGMzUmxjakFRQVVKUENnbHpaV053TWpVMmF6RVNJSGtvUHRqanRTWFo2YzFEZ1lyY3lJbkYzWDgzZ0hFMzFnRGZtQW5yZ204aUdpQ1VPQmt5TjlMV2xZc0hJVTkzdEp5MXdVdU53ZUlfWTViSlNxTm1aWFY4NHciLCJhdWQiOiJkb21haW4iLCJ2cCI6eyJAY29udGV4dCI6WyJodHRwczpcL1wvd3d3LnczLm9yZ1wvMjAxOFwvY3JlZGVudGlhbHNcL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZVByZXNlbnRhdGlvbiJdLCJ2ZXJpZmlhYmxlQ3JlZGVudGlhbCI6WyJleUpoYkdjaU9pSkZVekkxTmtzaWZRLmV5SnBjM01pT2lKa2FXUTZjSEpwYzIwNk1qVTNNVGxoT1RaaU1UVXhNakEzTVRZNU9ERmhPRFF6TUdGa01HTmlPVFk0WkdRMU16UXdOek0xT1ROak9HTmtNMll4WkRJM1lUWTRNRFJsWXpVd1pUcERjRzlEUTNCalEwVnNiMHRDVjNSc1pWTXdlRVZCU2tOVWQyOUtZekpXYW1ORVNURk9iWE40UldsQlJXOVRRMjQxZEhsRVlUWlpObkl0U1cxVGNYQktPRmt4YldvM1NrTXpYMjlWZWtVd1RubDVSV2xEUW05bmMyZE9ZV1ZTWkdORFVrZFFiR1U0TWxaMk9YUktaazUzYkRaeVp6WldZMmhTTTA5eGFHbFdZbFJoT0ZOWGQyOUhXVmhXTUdGRE1IaEZRVkpEVkhkdlNtTXlWbXBqUkVreFRtMXplRVZwUkUxclFtUTJSblJwYjBwck0xaFBSblV0WDJONU5WaHRVaTAwZEZWUk1rNU1SMmxYT0dGSlUyOXRhMUp2WnpaVFpHVTVVSGR1UnpCUk1GTkNWRzFHVTFSRVlsTkxRblpKVmpaRFZFeFljbXBKU25SMFpVZEpiVUZUV0VGdlNHSlhSbnBrUjFaNVRVSkJRbEZyT0V0RFdFNXNXVE5CZVU1VVduSk5Va2xuVHpjeE1HMTBNVmRmYVhoRWVWRk5NM2hKY3pkVWNHcE1RMDVQUkZGNFoxWm9lRFZ6YUdaTFRsZ3hiMkZKU0ZkUWNuYzNTVlZMYkdacFlsRjBlRFpLYXpSVVUycG5ZMWRPVDJaalQzUlZPVVE1VUhWYU4xUTVkQ0lzSW5OMVlpSTZJbVJwWkRwd2NtbHpiVHBpWldWaE5USXpOR0ZtTkRZNE1EUTNNVFJrT0dWaE9HVmpOemRpTmpaall6ZG1NMlU0TVRWak5qaGhZbUkwTnpWbU1qVTBZMlk1WXpNd05qSTJOell6T2tOelkwSkRjMUZDUlcxUlMwUXlSakZrUjJoc1ltNVNjRmt5UmpCaFZ6bDFUVUpCUlZGck9FdERXRTVzV1ROQmVVNVVXbkpOVWtsblpWTm5MVEpQVHpGS1pHNXdlbFZQUW1sMGVrbHBZMWhrWm5wbFFXTlVabGRCVGkxWlEyVjFRMko1U1dGSlNsRTBSMVJKTXpCMFlWWnBkMk5vVkRObE1HNU1XRUpUTkROQ05HbzVhbXh6YkV0dk1scHNaRmg2YWtWc2QwdENNakZvWXpOU2JHTnFRVkZCVlVwUVEyZHNlbHBYVG5kTmFsVXlZWHBGVTBsSWEyOVFkR3BxZEZOWVdqWmpNVVJuV1hKamVVbHVSak5ZT0ROblNFVXpNV2RFWm0xQmJuSm5iVGhwUjJsRFZVOUNhM2xPT1V4WGJGbHpTRWxWT1ROMFNua3hkMVYxVG5kbFNWOVpOV0pLVTNGT2JWcFlWamcwZHlJc0ltNWlaaUk2TVRZNE5UWXpNVGs1TlN3aVpYaHdJam94TmpnMU5qTTFOVGsxTENKMll5STZleUpqY21Wa1pXNTBhV0ZzVTNWaWFtVmpkQ0k2ZXlKaFpHUnBkR2x2Ym1Gc1VISnZjRElpT2lKVVpYTjBNeUlzSW1sa0lqb2laR2xrT25CeWFYTnRPbUpsWldFMU1qTTBZV1kwTmpnd05EY3hOR1E0WldFNFpXTTNOMkkyTm1Oak4yWXpaVGd4TldNMk9HRmlZalEzTldZeU5UUmpaamxqTXpBMk1qWTNOak02UTNOalFrTnpVVUpGYlZGTFJESkdNV1JIYUd4aWJsSndXVEpHTUdGWE9YVk5Ra0ZGVVdzNFMwTllUbXhaTTBGNVRsUmFjazFTU1dkbFUyY3RNazlQTVVwa2JuQjZWVTlDYVhSNlNXbGpXR1JtZW1WQlkxUm1WMEZPTFZsRFpYVkRZbmxKWVVsS1VUUkhWRWt6TUhSaFZtbDNZMmhVTTJVd2JreFlRbE0wTTBJMGFqbHFiSE5zUzI4eVdteGtXSHBxUld4M1MwSXlNV2hqTTFKc1kycEJVVUZWU2xCRFoyeDZXbGRPZDAxcVZUSmhla1ZUU1VocmIxQjBhbXAwVTFoYU5tTXhSR2RaY21ONVNXNUdNMWc0TTJkSVJUTXhaMFJtYlVGdWNtZHRPR2xIYVVOVlQwSnJlVTQ1VEZkc1dYTklTVlU1TTNSS2VURjNWWFZPZDJWSlgxazFZa3BUY1U1dFdsaFdPRFIzSW4wc0luUjVjR1VpT2xzaVZtVnlhV1pwWVdKc1pVTnlaV1JsYm5ScFlXd2lYU3dpUUdOdmJuUmxlSFFpT2xzaWFIUjBjSE02WEM5Y0wzZDNkeTUzTXk1dmNtZGNMekl3TVRoY0wyTnlaV1JsYm5ScFlXeHpYQzkyTVNKZGZYMC54MFNGMTdZMFZDRG10N0hjZU9kVHhmSGxvZnNabVkxOFJuNlZRYjAtci1rX0JtM2hUaTEtazJ2a2RqQjI1aGR4eVRDdnhhbS1Ba0FQLUFnM0FobjVOZyJdfSwibm9uY2UiOiJjaGFsbGVuZ2UifQ.pkxVLfL3xLcA_lP6kAFfN4SefD6X0fsKSbGNiXSNn4x7iPewC28zKVeIkrYCzQCV_wAcCrbyBu5KAwICxRsPjQ"]}""" - val presentationDefinitionRequest = """{"presentation_definition":{"id":"32f54163-7166-48f1-93d8-ff217bdb0653","input_descriptors":[{"id":"wa_driver_license","name":"Washington State Business License","purpose":"We can only allow licensed Washington State business representatives into the WA Business Conference","constraints":{"fields":[{"path":["${'$'}.credentialSubject.dateOfBirth","${'$'}.credentialSubject.dob","${'$'}.vc.credentialSubject.dateOfBirth","${'$'}.vc.credentialSubject.dob"]}]}}],"format":{"jwt":{"alg":["ES256K"]}}},"options":{"domain":"domain","challenge":"challenge"}}""" val credential = JWTCredential.fromJwtString( "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MjU3MTlhOTZiMTUxMjA3MTY5ODFhODQzMGFkMGNiOTY4ZGQ1MzQwNzM1OTNjOGNkM2YxZDI3YTY4MDRlYzUwZTpDcG9DQ3BjQ0Vsb0tCV3RsZVMweEVBSkNUd29KYzJWamNESTFObXN4RWlBRW9TQ241dHlEYTZZNnItSW1TcXBKOFkxbWo3SkMzX29VekUwTnl5RWlDQm9nc2dOYWVSZGNDUkdQbGU4MlZ2OXRKZk53bDZyZzZWY2hSM09xaGlWYlRhOFNXd29HWVhWMGFDMHhFQVJDVHdvSmMyVmpjREkxTm1zeEVpRE1rQmQ2RnRpb0prM1hPRnUtX2N5NVhtUi00dFVRMk5MR2lXOGFJU29ta1JvZzZTZGU5UHduRzBRMFNCVG1GU1REYlNLQnZJVjZDVExYcmpJSnR0ZUdJbUFTWEFvSGJXRnpkR1Z5TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTzcxMG10MVdfaXhEeVFNM3hJczdUcGpMQ05PRFF4Z1ZoeDVzaGZLTlgxb2FJSFdQcnc3SVVLbGZpYlF0eDZKazRUU2pnY1dOT2ZjT3RVOUQ5UHVaN1Q5dCIsInN1YiI6ImRpZDpwcmlzbTpiZWVhNTIzNGFmNDY4MDQ3MTRkOGVhOGVjNzdiNjZjYzdmM2U4MTVjNjhhYmI0NzVmMjU0Y2Y5YzMwNjI2NzYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklnZVNnLTJPTzFKZG5welVPQml0eklpY1hkZnplQWNUZldBTi1ZQ2V1Q2J5SWFJSlE0R1RJMzB0YVZpd2NoVDNlMG5MWEJTNDNCNGo5amxzbEtvMlpsZFh6akVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lIa29QdGpqdFNYWjZjMURnWXJjeUluRjNYODNnSEUzMWdEZm1BbnJnbThpR2lDVU9Ca3lOOUxXbFlzSElVOTN0Snkxd1V1TndlSV9ZNWJKU3FObVpYVjg0dyIsIm5iZiI6MTY4NTYzMTk5NSwiZXhwIjoxNjg1NjM1NTk1LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsUHJvcDIiOiJUZXN0MyIsImlkIjoiZGlkOnByaXNtOmJlZWE1MjM0YWY0NjgwNDcxNGQ4ZWE4ZWM3N2I2NmNjN2YzZTgxNWM2OGFiYjQ3NWYyNTRjZjljMzA2MjY3NjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdlU2ctMk9PMUpkbnB6VU9CaXR6SWljWGRmemVBY1RmV0FOLVlDZXVDYnlJYUlKUTRHVEkzMHRhVml3Y2hUM2UwbkxYQlM0M0I0ajlqbHNsS28yWmxkWHpqRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUhrb1B0amp0U1haNmMxRGdZcmN5SW5GM1g4M2dIRTMxZ0RmbUFucmdtOGlHaUNVT0JreU45TFdsWXNISVU5M3RKeTF3VXVOd2VJX1k1YkpTcU5tWlhWODR3In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.x0SF17Y0VCDmt7HceOdTxfHlofsZmY18Rn6VQb0-r-k_Bm3hTi1-k2vkdjB25hdxyTCvxam-AkAP-Ag3Ahn5Ng" ) - // Mock createPresentationSubmission response - `when`(polluxMock.createJWTPresentationSubmission(any(), any(), any())).thenReturn( -// `when`(polluxMock.createJWTPresentationSubmission(presentationDefinitionRequest, credential, privateKeys.first())).thenReturn( - presentationSubmission - ) val agent = EdgeAgent( apollo = apolloMock, @@ -1385,8 +1379,16 @@ class EdgeAgentTests { val actualPresentationSubmission = Json.decodeFromString(attachmentDataString) assertEquals( - expectedPresentationSubmission, - actualPresentationSubmission + expectedPresentationSubmission.presentationSubmission.definitionId, + actualPresentationSubmission.presentationSubmission.definitionId + ) + assertContentEquals( + expectedPresentationSubmission.presentationSubmission.descriptorMap, + actualPresentationSubmission.presentationSubmission.descriptorMap + ) + assertEquals( + expectedPresentationSubmission.verifiablePresentation.size, + actualPresentationSubmission.verifiablePresentation.size ) } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index 23aacf6be..fa16a293a 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -15,15 +15,14 @@ import kotlinx.coroutines.launch import org.hyperledger.identus.walletsdk.db.AppDatabase import org.hyperledger.identus.walletsdk.db.DatabaseClient import org.hyperledger.identus.walletsdk.domain.DIDCOMM_MESSAGING -import org.hyperledger.identus.walletsdk.domain.models.AnoncredsInputFieldFilter -import org.hyperledger.identus.walletsdk.domain.models.AnoncredsPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.DID import org.hyperledger.identus.walletsdk.domain.models.DIDDocument +import org.hyperledger.identus.walletsdk.domain.models.InputFieldFilter +import org.hyperledger.identus.walletsdk.domain.models.JWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential -import org.hyperledger.identus.walletsdk.domain.models.RequestedAttributes import org.hyperledger.identus.walletsdk.edgeagent.DIDCOMM1 import org.hyperledger.identus.walletsdk.edgeagent.EdgeAgentError import org.hyperledger.identus.walletsdk.edgeagent.protocols.ProtocolType @@ -99,43 +98,43 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application CoroutineScope(Dispatchers.Default).launch { val sdk = Sdk.getInstance() // JWT presentation request -// sdk.agent.initiatePresentationRequest( -// type = CredentialType.JWT, -// toDID = DID(toDID), -// presentationClaims = JWTPresentationClaims( -// claims = mapOf( -// "emailAddress" to InputFieldFilter( -// type = "string", -// pattern = "cristian.castro@iohk.io" -// ) -// ) -// ), -// domain = "domain", -// challenge = "challenge" -// ) - - // Anoncreds presentation request sdk.agent.initiatePresentationRequest( - type = CredentialType.ANONCREDS_PROOF_REQUEST, + type = CredentialType.JWT, toDID = DID(toDID), - presentationClaims = AnoncredsPresentationClaims( - predicates = mapOf( - "0_age" to AnoncredsInputFieldFilter( + presentationClaims = JWTPresentationClaims( + claims = mapOf( + "emailAddress" to InputFieldFilter( type = "string", - name = "age", - gte = 18 - ) - ), - attributes = mapOf( - "0_name" to RequestedAttributes( - "name", - setOf("name"), - emptyMap(), - null + pattern = "cristian.castro@iohk.io" ) ) - ) + ), + domain = "domain", + challenge = "challenge" ) + + // Anoncreds presentation request +// sdk.agent.initiatePresentationRequest( +// type = CredentialType.ANONCREDS_PROOF_REQUEST, +// toDID = DID(toDID), +// presentationClaims = AnoncredsPresentationClaims( +// predicates = mapOf( +// "0_age" to AnoncredsInputFieldFilter( +// type = "string", +// name = "age", +// gte = 18 +// ) +// ), +// attributes = mapOf( +// "0_name" to RequestedAttributes( +// "name", +// setOf("name"), +// emptyMap(), +// null +// ) +// ) +// ) +// ) } } From 2ca715504f07239e7d8cc119b45f612a06bceb10 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 22 Aug 2024 12:08:45 -0400 Subject: [PATCH 02/22] feat: verify jwt verification after changes Signed-off-by: Cristian G --- .../identus/walletsdk/pollux/PolluxImpl.kt | 10 +++++----- .../sampleapp/ui/messages/MessagesViewModel.kt | 14 +++++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 2c159be73..ea005e8cd 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -1033,7 +1033,7 @@ open class PolluxImpl( ?.toTypedArray() ?: arrayOf() } - return@let PresentationSubmission(submission, arrayStrings) + PresentationSubmission(submission, arrayStrings) } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") val presentationDefinitionRequestString = @@ -1080,11 +1080,11 @@ open class PolluxImpl( value?.let { vc -> val verifiableCredential = JWTCredential.fromJwtString(vc as String) - val isRevoked = isCredentialRevoked(verifiableCredential) +// val isRevoked = isCredentialRevoked(verifiableCredential) - if (isRevoked) { - throw PolluxError.VerificationUnsuccessful("Provided credential is revoked") - } +// if (isRevoked) { +// throw PolluxError.VerificationUnsuccessful("Provided credential is revoked") +// } if (verifiableCredential.subject != issuer) { throw PolluxError.VerificationUnsuccessful("Invalid submission,") diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index fa16a293a..49cc1de95 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -192,11 +192,15 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application viewModelScope.launch(handler) { messages.value?.find { it.id == uiMessage.id }?.let { message -> val sdk = Sdk.getInstance() - val valid = sdk.agent.handlePresentation(message) - if (valid) { - liveData.postValue("Valid!") - } else { - liveData.postValue("Not valid!") + try { + val valid = sdk.agent.handlePresentation(message) + if (valid) { + liveData.postValue("Valid!") + } else { + liveData.postValue("Not valid!") + } + } catch (e: Exception) { + liveData.postValue("Not valid. ${e.message}") } } } From 9e351e1572b8053f403fe4d900f5f9e29d4c4fa4 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 5 Sep 2024 20:15:39 -0400 Subject: [PATCH 03/22] feat: request credential support for SD-JWT Signed-off-by: Cristian G --- .../org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt | 2 +- .../org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt | 4 +++- .../identus/walletsdk/pollux/models/JWTCredential.kt | 2 +- sampleapp/src/main/res/values/strings.xml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index f22196080..7daabb7c9 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -643,7 +643,7 @@ open class EdgeAgent { } return when (val type = pollux.extractCredentialFormatFromMessage(offer.attachments)) { - CredentialType.JWT -> { + CredentialType.JWT, CredentialType.SDJWT -> { val privateKeyKeyPath = pluto.getPrismDIDKeyPathIndex(did).first() val keyPair = Secp256k1KeyPair.generateKeyPair( diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 9f856092d..c2a8a8e4c 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -325,7 +325,8 @@ open class PolluxImpl( CredentialType.ANONCREDS_OFFER.type, CredentialType.ANONCREDS_REQUEST.type, CredentialType.ANONCREDS_ISSUE.type, - CredentialType.ANONCREDS_PROOF_REQUEST.type + CredentialType.ANONCREDS_PROOF_REQUEST.type, + CredentialType.SDJWT.type ) val foundFormat = formats.find { it.format in desiredFormats } return foundFormat?.format?.let { format -> @@ -335,6 +336,7 @@ open class PolluxImpl( CredentialType.ANONCREDS_REQUEST.type -> CredentialType.ANONCREDS_REQUEST CredentialType.ANONCREDS_ISSUE.type -> CredentialType.ANONCREDS_ISSUE CredentialType.ANONCREDS_PROOF_REQUEST.type -> CredentialType.ANONCREDS_PROOF_REQUEST + CredentialType.SDJWT.type -> CredentialType.SDJWT else -> throw Error("$format is not a valid credential type") } } ?: throw Error("Unknown credential type") diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt index 4a7c476bc..63f41f1da 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/JWTCredential.kt @@ -213,7 +213,7 @@ data class JWTCredential @JvmOverloads constructor( verifiablePresentation = arrayOf(presentationJwt) ) ) - } ?: throw PolluxError.NullField("CredentialSubject") + } ?: throw PolluxError.NonNullableError("CredentialSubject") } private fun vcPresentation(request: ByteArray, subjectDID: DID, exportableKey: PrivateKey): String { diff --git a/sampleapp/src/main/res/values/strings.xml b/sampleapp/src/main/res/values/strings.xml index e01d620c2..8d3555fcb 100644 --- a/sampleapp/src/main/res/values/strings.xml +++ b/sampleapp/src/main/res/values/strings.xml @@ -31,7 +31,7 @@ Mediator DID: Agent status: - did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjY4LjExMzo4MDgwIiwiYSI6WyJkaWRjb21tL3YyIl19fQ + did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjY4LjExMzo4MDgwIiwiYSI6WyJkaWRjb21tL3YyIl19fQ.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC42OC4xMTM6ODA4MC93cyIsImEiOlsiZGlkY29tbS92MiJdfX0 Credentials Host: From cf9e827dd73611404b5b23da5a7ef5d2626923f0 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Tue, 10 Sep 2024 10:05:54 -0400 Subject: [PATCH 04/22] feat: verification sd-jwt Signed-off-by: Cristian G --- edge-agent-sdk/build.gradle.kts | 1 + .../walletsdk/castor/shared/CastorShared.kt | 2 +- .../models/keyManagement/VerifiableKey.kt | 2 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 96 ++++++++++++++++++- .../walletsdk/pluto/CredentialRecovery.kt | 4 +- .../walletsdk/pluto/PlutoBackupTask.kt | 4 + .../walletsdk/pluto/PlutoRestoreTask.kt | 11 ++- .../identus/walletsdk/pollux/PolluxImpl.kt | 7 +- .../pollux/models/SDJWTCredential.kt | 27 +++--- .../ui/credentials/CredentialsAdapter.kt | 6 ++ .../ui/credentials/CredentialsFragment.kt | 1 + .../ui/credentials/CredentialsViewModel.kt | 7 ++ .../ui/messages/MessagesViewModel.kt | 20 +++- .../main/res/layout/fragment_credentials.xml | 12 +++ 14 files changed, 175 insertions(+), 25 deletions(-) diff --git a/edge-agent-sdk/build.gradle.kts b/edge-agent-sdk/build.gradle.kts index 291b36ff3..613fd5d16 100644 --- a/edge-agent-sdk/build.gradle.kts +++ b/edge-agent-sdk/build.gradle.kts @@ -135,6 +135,7 @@ kotlin { exclude(group = "com.nimbusds", module = "nimbus-jose-jwt") } implementation(kotlin("reflect")) + implementation("org.bouncycastle:bcprov-jdk15on:1.68") implementation("com.apicatalog:titanium-json-ld-jre8:1.4.0") implementation("org.glassfish:jakarta.json:2.0.1") diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt index 8cd2a21fd..4984a2e3c 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt @@ -439,7 +439,7 @@ internal class CastorShared { * @return [OctetPublicKey]. */ @JvmStatic - private fun octetPublicKey(keyPair: KeyPair): OctetPublicKey { + internal fun octetPublicKey(keyPair: KeyPair): OctetPublicKey { val curve = when (keyPair::class) { Secp256k1KeyPair::class -> { Curve.SECP256K1 diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt index 081445537..1e3184769 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt @@ -11,4 +11,4 @@ interface VerifiableKey { * @return a boolean which tell us if message and signature match */ fun verify(message: ByteArray, signature: ByteArray): Boolean -} +} \ No newline at end of file diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 7daabb7c9..3b31b9499 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -11,12 +11,25 @@ import com.nimbusds.jose.JWEDecrypter import com.nimbusds.jose.JWEEncrypter import com.nimbusds.jose.JWEHeader import com.nimbusds.jose.JWEObject +import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.Payload +import com.nimbusds.jose.crypto.Ed25519Signer import com.nimbusds.jose.crypto.X25519Decrypter import com.nimbusds.jose.crypto.X25519Encrypter +import com.nimbusds.jose.jwk.KeyUse import com.nimbusds.jose.jwk.OctetKeyPair import com.nimbusds.jose.util.Base64URL -import eu.europa.ec.eudi.sdjwt.SdJwt +import eu.europa.ec.eudi.sdjwt.SdJwtIssuer +import eu.europa.ec.eudi.sdjwt.exp +import eu.europa.ec.eudi.sdjwt.iat +import eu.europa.ec.eudi.sdjwt.iss +import eu.europa.ec.eudi.sdjwt.nimbus +import eu.europa.ec.eudi.sdjwt.plain +import eu.europa.ec.eudi.sdjwt.sd +import eu.europa.ec.eudi.sdjwt.sdJwt +import eu.europa.ec.eudi.sdjwt.serialize +import eu.europa.ec.eudi.sdjwt.structured +import eu.europa.ec.eudi.sdjwt.sub import eu.europa.ec.eudi.sdjwt.vc.SD_JWT_VC_TYPE import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.ContentType @@ -41,6 +54,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.put import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey @@ -116,6 +130,7 @@ import org.hyperledger.identus.walletsdk.pluto.models.backup.BackupV0_0_1 import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.CredentialRequestMeta import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.kotlincrypto.hash.sha2.SHA256 /** @@ -1086,7 +1101,8 @@ open class EdgeAgent { val presentationDefinitionRequest: String val attachmentDescriptor: AttachmentDescriptor when (type) { - CredentialType.JWT -> { + CredentialType.JWT, + CredentialType.SDJWT -> { if (domain == null) { throw EdgeAgentError.MissingOrNullFieldError("Domain", "initiatePresentationRequest parameters") } @@ -1156,9 +1172,43 @@ open class EdgeAgent { val presentationDefinitionRequestString = requestPresentation.attachments.firstNotNullOf { it.data.getDataAsJsonString() } - if (presentationDefinitionRequestString.contains("jwt") || - presentationDefinitionRequestString.contains("vc+sd-jwt") - ) { + if (presentationDefinitionRequestString.contains("vc+sd-jwt")) { + val didString = + credential.subject ?: throw Exception("Credential must contain subject") + + val storablePrivateKey = pluto.getDIDPrivateKeysByDID(DID(didString)).first().first() + ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) + val privateKey = + apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) + + credential as SDJWTCredential + val disclosingClaims = listOf() + credential.sdjwt.jwt.second[""] + + val presentationString = credential.presentation( + CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + presentationDefinitionRequestString.encodeToByteArray(), + listOf( +// CredentialOperationsOptions.DisclosingClaims(), + CredentialOperationsOptions.SubjectDID(DID(didString)), + CredentialOperationsOptions.ExportableKey(privateKey) + ) + ) + + val attachmentDescriptor = AttachmentDescriptor( + mediaType = "application/json", + format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, + data = AttachmentBase64(presentationString.base64UrlEncoded) + ) + + return Presentation( + body = Presentation.Body(), + attachments = arrayOf(attachmentDescriptor), + thid = requestPresentation.thid ?: requestPresentation.id, + from = requestPresentation.to, + to = requestPresentation.from + ) + } else if (presentationDefinitionRequestString.contains("jwt")) { // If the json can be used to instantiate a JWTPresentationDefinitionRequest, process the request // as JWT. val didString = @@ -1400,6 +1450,42 @@ open class EdgeAgent { } } + suspend fun createSDJWTCredential(): SDJWTCredential { + val keyPair = Ed25519KeyPair.generateKeyPair() + + val subject = createNewPrismDID() + println(subject.toString()) + + val octet = OctetKeyPair.Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(keyPair.publicKey.raw)) + .d(Base64URL.encode(keyPair.privateKey.raw)) + .keyUse(KeyUse.SIGNATURE) + .build() + + val issuer = SdJwtIssuer + .nimbus( + signer = Ed25519Signer(octet), + signAlgorithm = JWSAlgorithm.EdDSA + ) + val sdjwt = issuer.issue( + sdJwt { + plain { + sub(subject.toString()) + iss("did:prism:ce3403b5a733883035d6ec43ba075a41c9cc0a3257977d80c75d6319ade0ed70") + iat(1516239022) + exp(1735689661) + } + structured("emailAddress") { + sd { + put("emailAddress", "test@iohk.io") + } + } + } + ).getOrThrow().serialize() + val cred = SDJWTCredential.fromSDJwtString(sdjwt) + pluto.storeCredential(cred.toStorableCredential()) + return cred + } + /** * This method retrieves the link secret from Pluto. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/CredentialRecovery.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/CredentialRecovery.kt index c7a6cc517..7254b12cf 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/CredentialRecovery.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/CredentialRecovery.kt @@ -20,7 +20,8 @@ class CredentialRecovery(val restorationId: String, val credentialData: ByteArra enum class RestorationID(val value: String) { JWT("jwt+credential"), ANONCRED("anon+credential"), - W3C("w3c+credential"); + W3C("w3c+credential"), + SDJWT("sd-jwt+credential"); /** * Converts a RestorationID object to a BackUpRestorationId object from the PlutoRestoreTask class. @@ -32,6 +33,7 @@ enum class RestorationID(val value: String) { JWT -> PlutoRestoreTask.BackUpRestorationId.JWT ANONCRED -> PlutoRestoreTask.BackUpRestorationId.ANONCRED W3C -> PlutoRestoreTask.BackUpRestorationId.W3C + SDJWT -> PlutoRestoreTask.BackUpRestorationId.SDJWT } } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoBackupTask.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoBackupTask.kt index 5fef885d6..3a3428ce2 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoBackupTask.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoBackupTask.kt @@ -87,6 +87,10 @@ open class PlutoBackupTask(private val pluto: Pluto) { PlutoRestoreTask.BackUpRestorationId.W3C } + RestorationID.SDJWT.value -> { + PlutoRestoreTask.BackUpRestorationId.SDJWT + } + else -> { throw UnknownError.SomethingWentWrongError("Unknown restoration ID ${credentialRecovery.restorationId}") } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoRestoreTask.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoRestoreTask.kt index 769717278..cbdcea637 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoRestoreTask.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoRestoreTask.kt @@ -44,6 +44,7 @@ import org.hyperledger.identus.walletsdk.pluto.PlutoRestoreTask.BackUpMessage.Js import org.hyperledger.identus.walletsdk.pluto.models.backup.BackupV0_0_1 import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -96,6 +97,12 @@ open class PlutoRestoreTask( .toAnonCredential().toStorableCredential() } + BackUpRestorationId.SDJWT.value -> { + val sdjwtString = it.data.base64UrlDecoded + .replace("\"null\"", "null") + SDJWTCredential.fromSDJwtString(sdjwtString).toStorableCredential() + } + else -> { throw UnknownError.SomethingWentWrongError("Unknown recovery id: ${it.recoveryId}") } @@ -336,7 +343,8 @@ open class PlutoRestoreTask( enum class BackUpRestorationId(val value: String) { JWT("jwt"), ANONCRED("anoncred"), - W3C("w3c"); + W3C("w3c"), + SDJWT("sd-jwt+credential"); /** * Converts a BackUpRestorationId object to a RestorationID object from the RestorationID class. @@ -348,6 +356,7 @@ open class PlutoRestoreTask( JWT -> RestorationID.JWT ANONCRED -> RestorationID.ANONCRED W3C -> RestorationID.W3C + SDJWT -> RestorationID.SDJWT } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index c2a8a8e4c..06398c1a4 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -307,6 +307,10 @@ open class PolluxImpl( (credential as AnonCredential).toStorableCredential() } + CredentialType.SDJWT -> { + (credential as SDJWTCredential).toStorableCredential() + } + else -> { throw PolluxError.InvalidCredentialError() } @@ -739,7 +743,8 @@ open class PolluxImpl( val format: JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.PresentationFormat val inputDescriptor: JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor when (type) { - CredentialType.JWT -> { + CredentialType.JWT, + CredentialType.SDJWT-> { if (options !is JWTPresentationOptions) { throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation options must be ${JWTPresentationOptions::class.simpleName}") } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index 90702e226..191203db5 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -18,6 +18,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonTransformingSerializer import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonPrimitive +import org.hyperledger.identus.walletsdk.domain.models.ApolloError import org.hyperledger.identus.walletsdk.domain.models.Claim import org.hyperledger.identus.walletsdk.domain.models.ClaimType import org.hyperledger.identus.walletsdk.domain.models.Credential @@ -36,10 +37,10 @@ data class SDJWTCredential( get() = sdjwtString @Transient - override val issuer: String = sdjwt.jwt.second.get("iss").toString() + override val issuer: String = sdjwt.jwt.second["iss"]?.jsonPrimitive?.content ?: throw Exception("Most contain issuer") // TODO: Custom exception override val subject: String? - get() = sdjwt.jwt.second.get("sub").toString() + get() = sdjwt.jwt.second["sub"]?.jsonPrimitive?.content override val claims: Array get() { @@ -52,12 +53,12 @@ data class SDJWTCredential( override val properties: Map get() { val properties = mutableMapOf() - properties["nbf"] = sdjwt.jwt.second.get("nbf").toString() - properties["jti"] = sdjwt.jwt.second.get("sub").toString() - properties["aud"] = sdjwt.jwt.second.get("aud").toString() + properties["nbf"] = sdjwt.jwt.second["nbf"]?.jsonPrimitive?.content + properties["jti"] = sdjwt.jwt.second["sub"]?.jsonPrimitive?.content + properties["aud"] = sdjwt.jwt.second["aud"]?.jsonPrimitive?.content properties["id"] = id - sdjwt.jwt.second.get("exp").toString().let { properties["exp"] = it } + sdjwt.jwt.second["exp"]?.jsonPrimitive?.content.let { properties["exp"] = it } return properties.toMap() } @@ -83,7 +84,7 @@ data class SDJWTCredential( } /** - * Converts the current instance of [JWTCredential] to a [StorableCredential]. + * Converts the current instance of [SDJWTCredential] to a [StorableCredential]. * * @return The converted [StorableCredential]. */ @@ -93,7 +94,7 @@ data class SDJWTCredential( override val id: String get() = c.id override val recoveryId: String - get() = "jwt+credential" + get() = "sd-jwt+credential" override val credentialData: ByteArray get() = c.id.toByteArray() @@ -109,7 +110,7 @@ data class SDJWTCredential( override val credentialSchema: String? get() = null override val validUntil: String? - get() = c.sdjwt.jwt.second.get("exp").toString().toString() + get() = c.sdjwt.jwt.second["exp"]?.jsonPrimitive?.content override var revoked: Boolean? = c.revoked override val availableClaims: Array get() = c.claims.map { it.key }.toTypedArray() @@ -122,12 +123,12 @@ data class SDJWTCredential( override val properties: Map get() { val properties = mutableMapOf() - properties["nbf"] = sdjwt.jwt.second.get("nbf").toString() - properties["jti"] = sdjwt.jwt.second.get("jti").toString() - properties["aud"] = sdjwt.jwt.second.get("aud").toString() + properties["nbf"] = sdjwt.jwt.second["nbf"]?.jsonPrimitive?.content + properties["jti"] = sdjwt.jwt.second["jti"]?.jsonPrimitive?.content + properties["aud"] = sdjwt.jwt.second["aud"]?.jsonPrimitive?.content properties["id"] = id - sdjwt.jwt.second.get("exp").toString().let { properties["exp"] = it } + sdjwt.jwt.second["exp"]?.jsonPrimitive?.content.let { properties["exp"] = it } return properties.toMap() } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt index 9393f0ab8..7819d44fe 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt @@ -16,6 +16,7 @@ import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential class CredentialsAdapter( private var data: MutableList = mutableListOf(), @@ -100,6 +101,11 @@ class CredentialsAdapter( type.text = String.format(typeString, "Anoncred") expiryDate.text = String.format("Issuer: ${anon.credentialDefinitionID}") } + + SDJWTCredential::class -> { + val sdjwt = cred as SDJWTCredential + type.text = String.format(typeString, "SD-JWT") + } } } } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt index d6b4b8159..90959ec51 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt @@ -29,6 +29,7 @@ class CredentialsFragment : Fragment() { } } adapter = CredentialsAdapter(itemClickListener = isRevoked) + binding.createSdjwt.setOnClickListener { viewModel.createSdJwt() } binding.credentials.adapter = adapter return binding.root } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt index 8aa717fa3..2f3c00ad9 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.viewModelFactory import kotlinx.coroutines.launch import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.sampleapp.Sdk @@ -29,4 +30,10 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat Sdk.getInstance().agent.isCredentialRevoked(credential) } } + + fun createSdJwt() { + viewModelScope.launch { + Sdk.getInstance().agent.createSDJWTCredential() + } + } } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index 49cc1de95..babc14111 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -98,14 +98,30 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application CoroutineScope(Dispatchers.Default).launch { val sdk = Sdk.getInstance() // JWT presentation request +// sdk.agent.initiatePresentationRequest( +// type = CredentialType.JWT, +// toDID = DID(toDID), +// presentationClaims = JWTPresentationClaims( +// claims = mapOf( +// "emailAddress" to InputFieldFilter( +// type = "string", +// pattern = "cristian.castro@iohk.io" +// ) +// ) +// ), +// domain = "domain", +// challenge = "challenge" +// ) + + // SD-JWT presentation request sdk.agent.initiatePresentationRequest( - type = CredentialType.JWT, + type = CredentialType.SDJWT, toDID = DID(toDID), presentationClaims = JWTPresentationClaims( claims = mapOf( "emailAddress" to InputFieldFilter( type = "string", - pattern = "cristian.castro@iohk.io" + pattern = "test@iohk.io" ) ) ), diff --git a/sampleapp/src/main/res/layout/fragment_credentials.xml b/sampleapp/src/main/res/layout/fragment_credentials.xml index ec30c93a7..5add4678f 100644 --- a/sampleapp/src/main/res/layout/fragment_credentials.xml +++ b/sampleapp/src/main/res/layout/fragment_credentials.xml @@ -16,4 +16,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + From 63f547b23b4bc7005dffab6337206e05c685dcdb Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 19 Sep 2024 13:48:05 -0400 Subject: [PATCH 05/22] feat: SD-JWT verification Signed-off-by: Cristian G --- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 237 ++++++++------- .../JWTPresentationOptions.kt | 39 ++- .../identus/walletsdk/pollux/PolluxImpl.kt | 177 ++++++----- .../models/PresentationDefinitionRequest.kt | 281 +++++++++--------- .../pollux/models/SDJWTCredential.kt | 2 - .../walletsdk/edgeagent/EdgeAgentTests.kt | 143 +++++++++ .../walletsdk/pluto/BackupRestorationTests.kt | 6 + .../walletsdk/pollux/PolluxImplTest.kt | 48 ++- .../ui/messages/MessagesViewModel.kt | 22 +- 9 files changed, 646 insertions(+), 309 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index d3fd7b9cf..bc7ba587b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -124,6 +124,7 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsAnoncreds import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.RequestPresentation +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.Metadata import org.hyperledger.identus.walletsdk.logger.Logger @@ -133,8 +134,10 @@ import org.hyperledger.identus.walletsdk.pluto.PlutoRestoreTask import org.hyperledger.identus.walletsdk.pluto.models.backup.BackupV0_0_1 import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.CredentialRequestMeta +import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest import org.kotlincrypto.hash.sha2.SHA256 /** @@ -1174,21 +1177,32 @@ open class EdgeAgent { when (type) { CredentialType.JWT, CredentialType.SDJWT -> { - if (domain == null) { - throw EdgeAgentError.MissingOrNullFieldError("Domain", "initiatePresentationRequest parameters") - } - if (challenge == null) { - throw EdgeAgentError.MissingOrNullFieldError("Challenge", "initiatePresentationRequest parameters") + val options = if (type == CredentialType.JWT) { + if (domain == null) { + throw EdgeAgentError.MissingOrNullFieldError("Domain", "initiatePresentationRequest parameters") + } + if (challenge == null) { + throw EdgeAgentError.MissingOrNullFieldError( + "Challenge", + "initiatePresentationRequest parameters" + ) + } + JWTPresentationOptions( + jwt = arrayOf("ES256K"), + domain = domain, + challenge = challenge + ) + } else { + SDJWTPresentationOptions( + sdjwt = arrayOf("ES256k"), + presentationFrame = emptyMap() + ) } presentationDefinitionRequest = pollux.createPresentationDefinitionRequest( type = type, presentationClaims = presentationClaims, - options = JWTPresentationOptions( - jwt = arrayOf("ES256K"), - domain = domain, - challenge = challenge - ) + options = options ) attachmentDescriptor = AttachmentDescriptor( mediaType = "application/json", @@ -1243,106 +1257,129 @@ open class EdgeAgent { val presentationDefinitionRequestString = requestPresentation.attachments.firstNotNullOf { it.data.getDataAsJsonString() } - if (presentationDefinitionRequestString.contains("vc+sd-jwt")) { - val didString = - credential.subject ?: throw Exception("Credential must contain subject") - - val storablePrivateKey = pluto.getDIDPrivateKeysByDID(DID(didString)).first().first() - ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) - val privateKey = - apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) - - credential as SDJWTCredential - val disclosingClaims = listOf() - credential.sdjwt.jwt.second[""] - - val presentationString = credential.presentation( - CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, - presentationDefinitionRequestString.encodeToByteArray(), - listOf( -// CredentialOperationsOptions.DisclosingClaims(), - CredentialOperationsOptions.SubjectDID(DID(didString)), - CredentialOperationsOptions.ExportableKey(privateKey) - ) - ) + val json = Json.parseToJsonElement(presentationDefinitionRequestString).jsonObject + if (json.containsKey("presentation_definition")) { + val presentationDefinition = json["presentation_definition"]!!.jsonObject + val fromDID = requestPresentation.to ?: createNewPeerDID(updateMediator = true) + if (presentationDefinition.containsKey("format")) { + val format = presentationDefinition["format"]!!.jsonObject + if (format["jwt"] != null) { + if (credential !is JWTCredential) { + throw EdgeAgentError.CredentialNotValidForPresentationRequest() + } + // If the json can be used to instantiate a JWTPresentationDefinitionRequest, process the request + // as JWT. + val didString = + credential.subject ?: throw Exception("Credential must contain subject") + + val storablePrivateKey = pluto.getDIDPrivateKeysByDID(DID(didString)).first().first() + ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) + val privateKey = + apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) + + val presentationString = credential.presentation( + CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + presentationDefinitionRequestString.encodeToByteArray(), + listOf( + CredentialOperationsOptions.SubjectDID(DID(didString)), + CredentialOperationsOptions.ExportableKey(privateKey) + ) + ) - val attachmentDescriptor = AttachmentDescriptor( - mediaType = "application/json", - format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, - data = AttachmentBase64(presentationString.base64UrlEncoded) - ) + val attachmentDescriptor = AttachmentDescriptor( + mediaType = "application/json", + format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, + data = AttachmentBase64(presentationString.base64UrlEncoded) + ) - return Presentation( - body = Presentation.Body(), - attachments = arrayOf(attachmentDescriptor), - thid = requestPresentation.thid ?: requestPresentation.id, - from = requestPresentation.to, - to = requestPresentation.from - ) - } else if (presentationDefinitionRequestString.contains("jwt")) { - // If the json can be used to instantiate a JWTPresentationDefinitionRequest, process the request - // as JWT. - val didString = - credential.subject ?: throw Exception("Credential must contain subject") - - val storablePrivateKey = pluto.getDIDPrivateKeysByDID(DID(didString)).first().first() - ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) - val privateKey = - apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) - - val presentationString = credential.presentation( - CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, - presentationDefinitionRequestString.encodeToByteArray(), - listOf( - CredentialOperationsOptions.SubjectDID(DID(didString)), - CredentialOperationsOptions.ExportableKey(privateKey) - ) - ) + val fromDID = requestPresentation.to ?: createNewPeerDID(updateMediator = true) + return Presentation( + body = Presentation.Body(), + attachments = arrayOf(attachmentDescriptor), + thid = requestPresentation.thid ?: requestPresentation.id, + from = fromDID, + to = requestPresentation.from + ) + } else if (format["sdJwt"] != null) { + if (credential !is SDJWTCredential) { + throw EdgeAgentError.CredentialNotValidForPresentationRequest() + } - val attachmentDescriptor = AttachmentDescriptor( - mediaType = "application/json", - format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, - data = AttachmentBase64(presentationString.base64UrlEncoded) - ) + val didString = + credential.subject ?: throw Exception("Credential must contain subject") - val fromDID = requestPresentation.to ?: createNewPeerDID(updateMediator = true) - return Presentation( - body = Presentation.Body(), - attachments = arrayOf(attachmentDescriptor), - thid = requestPresentation.thid ?: requestPresentation.id, - from = fromDID, - to = requestPresentation.from - ) - } else { - val linkSecret = getLinkSecret() + val storablePrivateKey = pluto.getDIDPrivateKeysByDID(DID(didString)).first().first() + ?: throw EdgeAgentError.CannotFindDIDPrivateKey(didString) + val privateKey = + apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) - val presentationSubmissionProof = credential.presentation( - CredentialType.ANONCREDS_PROOF_REQUEST.type, - presentationDefinitionRequestString.toByteArray(), - listOf( - CredentialOperationsOptions.LinkSecret("", linkSecret), - CredentialOperationsOptions.SchemaDownloader(api), - CredentialOperationsOptions.CredentialDefinitionDownloader(api) - ) - ) + val presentationDefinitionRequest = Json.decodeFromString( + presentationDefinitionRequestString + ) - val attachmentDescriptor = AttachmentDescriptor( - mediaType = "application/json", - format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, - data = AttachmentBase64(presentationSubmissionProof.base64UrlEncoded) - ) - val fromDID = requestPresentation.to ?: createNewPeerDID(updateMediator = true) - return Presentation( - body = Presentation.Body(), - attachments = arrayOf(attachmentDescriptor), - thid = requestPresentation.thid ?: requestPresentation.id, - from = fromDID, - to = requestPresentation.from - ) + val disclosingClaims = presentationDefinitionRequest.options?.let { options -> + options.presentationFrame.map { presentationFrame -> + presentationFrame.key + } + } ?: emptyList() + + val presentationString = credential.presentation( + CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + presentationDefinitionRequestString.encodeToByteArray(), + listOf( + CredentialOperationsOptions.DisclosingClaims(disclosingClaims), + CredentialOperationsOptions.SubjectDID(DID(didString)), + CredentialOperationsOptions.ExportableKey(privateKey) + ) + ) + + val attachmentDescriptor = AttachmentDescriptor( + mediaType = "application/json", + format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, + data = AttachmentBase64(presentationString.base64UrlEncoded) + ) + + return Presentation( + body = Presentation.Body(), + attachments = arrayOf(attachmentDescriptor), + thid = requestPresentation.thid ?: requestPresentation.id, + from = fromDID, + to = requestPresentation.from + ) + } else { + val linkSecret = getLinkSecret() + + val presentationSubmissionProof = credential.presentation( + CredentialType.ANONCREDS_PROOF_REQUEST.type, + presentationDefinitionRequestString.toByteArray(), + listOf( + CredentialOperationsOptions.LinkSecret("", linkSecret), + CredentialOperationsOptions.SchemaDownloader(api), + CredentialOperationsOptions.CredentialDefinitionDownloader(api) + ) + ) + + val attachmentDescriptor = AttachmentDescriptor( + mediaType = "application/json", + format = CredentialType.PRESENTATION_EXCHANGE_SUBMISSION.type, + data = AttachmentBase64(presentationSubmissionProof.base64UrlEncoded) + ) + + return Presentation( + body = Presentation.Body(), + attachments = arrayOf(attachmentDescriptor), + thid = requestPresentation.thid ?: requestPresentation.id, + from = fromDID, + to = requestPresentation.from + ) + } + } } } catch (e: Exception) { throw EdgeAgentError.CredentialNotValidForPresentationRequest() } + // TODO: handle this point + throw EdgeAgentError.CredentialNotValidForPresentationRequest() } suspend fun handlePresentation(msg: Message): Boolean { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt index 920630860..4d55f28c7 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt @@ -6,11 +6,13 @@ import org.hyperledger.identus.walletsdk.domain.models.CredentialType sealed interface PresentationOptions { val type: CredentialType + val name: String? + val purpose: String? } data class JWTPresentationOptions( - val name: String? = "Presentation", - val purpose: String = "Presentation definition", + override val name: String = "Presentation", + override val purpose: String = "Presentation definition", val jwt: Array = arrayOf("ES256K"), val domain: String, val challenge: String @@ -40,9 +42,40 @@ data class JWTPresentationOptions( } } +data class SDJWTPresentationOptions( + override val name: String? = "Presentation", + override val purpose: String = "Presentation definition", + val sdjwt: Array = arrayOf("ES256K"), + val presentationFrame: Map +) : PresentationOptions { + override val type: CredentialType = CredentialType.SDJWT + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as SDJWTPresentationOptions + + if (name != other.name) return false + if (purpose != other.purpose) return false + if (presentationFrame != other.presentationFrame) return false + return sdjwt.contentEquals(other.sdjwt) + } + + override fun hashCode(): Int { + var result = name?.hashCode() ?: 0 + result = 31 * result + purpose.hashCode() + result = 31 * result + (sdjwt.contentHashCode()) + result = 31 * result + presentationFrame.hashCode() + return result + } +} + data class AnoncredsPresentationOptions( // TODO: This should be a nonce from the anoncred wrapper - val nonce: String + val nonce: String, + override val name: String? = null, + override val purpose: String? = null ) : PresentationOptions { override val type: CredentialType = CredentialType.ANONCREDS_PROOF_REQUEST } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 69ef413ad..fea97e766 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -95,6 +95,7 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsAnoncreds import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl import org.hyperledger.identus.walletsdk.logger.LogComponent @@ -105,8 +106,10 @@ import org.hyperledger.identus.walletsdk.pollux.models.DescriptorItemFormat import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.JWTProofType +import org.hyperledger.identus.walletsdk.pollux.models.PresentationDefinition import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.VerificationKeyType import org.hyperledger.identus.walletsdk.pollux.models.W3CCredential import org.hyperledger.identus.walletsdk.pollux.utils.BitString @@ -753,82 +756,114 @@ open class PolluxImpl( presentationClaims: PresentationClaims, options: PresentationOptions ): String { - val format: JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.PresentationFormat - val inputDescriptor: JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor + val format: PresentationDefinition.InputDescriptor.PresentationFormat + val inputDescriptor: PresentationDefinition.InputDescriptor when (type) { CredentialType.JWT, - CredentialType.SDJWT-> { - if (options !is JWTPresentationOptions) { - throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation options must be ${JWTPresentationOptions::class.simpleName}") - } - if (presentationClaims !is JWTPresentationClaims) { - throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation claims must be ${JWTPresentationClaims::class.simpleName}") - } - val jwt = options.jwt - if (jwt.isEmpty()) { - throw PolluxError.InvalidJWTPresentationDefinitionError("Presentation option must contain at least one valid JWT alg that is not empty.") - } - val paths = presentationClaims.claims.keys - val mutableListFields: MutableList = - paths.map { path -> - JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.Constraints.Field( - path = arrayOf("$.vc.credentialSubject.$path", "$.credentialSubject.$path"), - id = UUID.randomUUID().toString(), - optional = false, - filter = presentationClaims.claims[path], - name = path, - ) - } as MutableList - - presentationClaims.issuer?.let { issuer -> - mutableListFields.add( - JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.Constraints.Field( - path = arrayOf("$.issuer", "$.iss", "$.vc.iss", "$.vc.issuer"), - optional = false, - id = UUID.randomUUID().toString(), - filter = InputFieldFilter( - type = "String", - pattern = issuer - ), - name = "issuer" - ) - ) + CredentialType.SDJWT -> { + if (options.name == null || options.purpose == null) { + throw Exception("") } + if (options is JWTPresentationOptions || options is SDJWTPresentationOptions) { + if (presentationClaims !is JWTPresentationClaims) { + throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation claims must be ${JWTPresentationClaims::class.simpleName}") + } - val constraints = JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.Constraints( - fields = mutableListFields.toTypedArray(), - limitDisclosure = - JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED - ) - - format = - JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.PresentationFormat( - jwt = jwt.let { - JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.JwtFormat( - jwt.toList() + val paths = presentationClaims.claims.keys + val mutableListFields: MutableList = + paths.map { path -> + PresentationDefinition.InputDescriptor.Constraints.Field( + path = arrayOf("$.vc.credentialSubject.$path", "$.credentialSubject.$path"), + id = UUID.randomUUID().toString(), + optional = false, + filter = presentationClaims.claims[path], + name = path, ) - } - ) + } as MutableList + + presentationClaims.issuer?.let { issuer -> + mutableListFields.add( + PresentationDefinition.InputDescriptor.Constraints.Field( + path = arrayOf("$.issuer", "$.iss", "$.vc.iss", "$.vc.issuer"), + optional = false, + id = UUID.randomUUID().toString(), + filter = InputFieldFilter( + type = "String", + pattern = issuer + ), + name = "issuer" + ) + ) + } - inputDescriptor = JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor( - name = options.name, - purpose = options.purpose, - constraints = constraints, - format = format - ) + val constraints = + PresentationDefinition.InputDescriptor.Constraints( + fields = mutableListFields.toTypedArray(), + limitDisclosure = + PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED + ) - return Json.encodeToString( - JWTPresentationDefinitionRequest( - presentationDefinition = JWTPresentationDefinitionRequest.PresentationDefinition( - inputDescriptors = arrayOf(inputDescriptor), + if (type == CredentialType.JWT) { + options as JWTPresentationOptions + val jwt = options.jwt + if (jwt.isEmpty()) { + throw PolluxError.InvalidJWTPresentationDefinitionError("Presentation option must contain at least one valid JWT alg that is not empty.") + } + val format = PresentationDefinition.InputDescriptor.PresentationFormat( + jwt = jwt.let { + PresentationDefinition.InputDescriptor.JwtFormat( + jwt.toList() + ) + } + ) + inputDescriptor = PresentationDefinition.InputDescriptor( + name = options.name, + purpose = options.purpose, + constraints = constraints, format = format - ), - options = JWTPresentationDefinitionRequest.PresentationDefinitionOptions( - domain = options.domain, - challenge = options.challenge ) - ) - ) + return Json.encodeToString( + JWTPresentationDefinitionRequest( + presentationDefinition = PresentationDefinition( + inputDescriptors = arrayOf(inputDescriptor), + format = format + ), + options = JWTPresentationDefinitionRequest.PresentationDefinitionOptions( + domain = options.domain, + challenge = options.challenge + ) + ) + ) + } else { + options as SDJWTPresentationOptions + val sdjwt = options.sdjwt + val format = PresentationDefinition.InputDescriptor.PresentationFormat( + sdjwt = sdjwt.let { + PresentationDefinition.InputDescriptor.JwtFormat( + sdjwt.toList() + ) + } + ) + inputDescriptor = PresentationDefinition.InputDescriptor( + name = options.name, + purpose = options.purpose, + constraints = constraints, + format = format + + ) + return Json.encodeToString( + SDJWTPresentationDefinitionRequest( + presentationDefinition = PresentationDefinition( + inputDescriptors = arrayOf(inputDescriptor), + format = format + ), + options = SDJWTPresentationDefinitionRequest.PresentationDefinitionOptions( + presentationFrame = options.presentationFrame + ) + ) + ) + } + } } CredentialType.ANONCREDS_PROOF_REQUEST -> { @@ -886,10 +921,14 @@ open class PolluxImpl( else -> { throw PolluxError.CredentialTypeNotSupportedError( "Credential type ${type.type} not supported. " + - "Must be ${CredentialType.JWT.type} or ${CredentialType.ANONCREDS_PROOF_REQUEST.type}" + "Must be ${CredentialType.JWT.type}, ${CredentialType.SDJWT.type} or ${CredentialType.ANONCREDS_PROOF_REQUEST.type}" ) } } + throw PolluxError.CredentialTypeNotSupportedError( + "Credential type ${type.type} not supported. " + + "Must be ${CredentialType.JWT.type}, ${CredentialType.SDJWT.type} or ${CredentialType.ANONCREDS_PROOF_REQUEST.type}" + ) } // TODO: Deprecate, the new way to create JWT presentation submission is using the credential. ProvableCredential.presentation() @@ -1136,7 +1175,7 @@ open class PolluxImpl( if (inputDescriptor != null) { val constraints = inputDescriptor.constraints val fields = constraints.fields - if (constraints.limitDisclosure == JWTPresentationDefinitionRequest.PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { + if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { fields?.forEach { field -> val optional = field.optional if (!optional) { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt index 970d7c477..74a8957f9 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt @@ -40,108 +40,57 @@ data class JWTPresentationDefinitionRequest( val domain: String, val challenge: String ) +} + +@Serializable +data class SDJWTPresentationDefinitionRequest( + @SerialName("presentation_definition") + val presentationDefinition: PresentationDefinition, + val options: PresentationDefinitionOptions? = null +) : PresentationDefinitionRequest { @Serializable - data class PresentationDefinition( - val id: String? = UUID.randomUUID().toString(), - @SerialName("input_descriptors") - val inputDescriptors: Array, - val format: InputDescriptor.PresentationFormat + data class PresentationDefinitionOptions( + val presentationFrame: Map + ) +} + +@Serializable +data class PresentationDefinition( + val id: String? = UUID.randomUUID().toString(), + @SerialName("input_descriptors") + val inputDescriptors: Array, + val format: InputDescriptor.PresentationFormat +) { + @Serializable + data class InputDescriptor( + val id: String = UUID.randomUUID().toString(), + val name: String? = null, + val purpose: String? = null, + val format: PresentationFormat? = null, + val constraints: Constraints ) { + @Serializable - data class InputDescriptor( - val id: String = UUID.randomUUID().toString(), - val name: String? = null, - val purpose: String? = null, - val format: PresentationFormat? = null, - val constraints: Constraints + data class Constraints @JvmOverloads constructor( + val fields: Array? = null, + @SerialName("limit_disclosure") + val limitDisclosure: LimitDisclosure? = null ) { - @Serializable - data class Constraints @JvmOverloads constructor( - val fields: Array? = null, - @SerialName("limit_disclosure") - val limitDisclosure: LimitDisclosure? = null + data class Field @JvmOverloads constructor( + val path: Array, + val id: String? = null, + val purpose: String? = null, + val name: String? = null, + val filter: InputFieldFilter? = null, + val optional: Boolean = false ) { @Serializable - data class Field @JvmOverloads constructor( - val path: Array, - val id: String? = null, - val purpose: String? = null, - val name: String? = null, - val filter: InputFieldFilter? = null, - val optional: Boolean = false - ) { - @Serializable - data class Filter( - val type: String, - val pattern: String - ) - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (javaClass != other?.javaClass) { - return false - } - - other as Field - - if (!path.contentEquals(other.path)) { - return false - } - if (id != other.id) { - return false - } - if (purpose != other.purpose) { - return false - } - if (name != other.name) { - return false - } - if (filter != other.filter) { - return false - } - if (optional != other.optional) { - return false - } - - return true - } - - override fun hashCode(): Int { - var result = path.contentHashCode() - result = 31 * result + (id?.hashCode() ?: 0) - result = 31 * result + (purpose?.hashCode() ?: 0) - result = 31 * result + (name?.hashCode() ?: 0) - result = 31 * result + (filter?.hashCode() ?: 0) - result = 31 * result + optional.hashCode() - return result - } - } - - @Serializable(with = LimitDisclosure.LimitDisclosureSerializer::class) - enum class LimitDisclosure(val value: String) { - REQUIRED("required"), - PREFERRED("preferred"); - - // Custom serializer for the enum - object LimitDisclosureSerializer : KSerializer { - override val descriptor: SerialDescriptor = - PrimitiveSerialDescriptor("LimitDisclosure", PrimitiveKind.STRING) - - override fun serialize(encoder: Encoder, value: LimitDisclosure) { - encoder.encodeString(value.value) - } - - override fun deserialize(decoder: Decoder): LimitDisclosure { - val stringValue = decoder.decodeString() - return entries.firstOrNull { it.value == stringValue } - ?: throw SerializationException("Unknown value: $stringValue") - } - } - } + data class Filter( + val type: String, + val pattern: String + ) override fun equals(other: Any?): Boolean { if (this === other) { @@ -151,19 +100,24 @@ data class JWTPresentationDefinitionRequest( return false } - other as Constraints + other as Field - if (fields != null) { - if (other.fields == null) { - return false - } - if (!fields.contentEquals(other.fields)) { - return false - } - } else if (other.fields != null) { + if (!path.contentEquals(other.path)) { + return false + } + if (id != other.id) { return false } - if (limitDisclosure != other.limitDisclosure) { + if (purpose != other.purpose) { + return false + } + if (name != other.name) { + return false + } + if (filter != other.filter) { + return false + } + if (optional != other.optional) { return false } @@ -171,48 +125,109 @@ data class JWTPresentationDefinitionRequest( } override fun hashCode(): Int { - var result = fields?.contentHashCode() ?: 0 - result = 31 * result + (limitDisclosure?.hashCode() ?: 0) + var result = path.contentHashCode() + result = 31 * result + (id?.hashCode() ?: 0) + result = 31 * result + (purpose?.hashCode() ?: 0) + result = 31 * result + (name?.hashCode() ?: 0) + result = 31 * result + (filter?.hashCode() ?: 0) + result = 31 * result + optional.hashCode() return result } } - @Serializable - data class PresentationFormat( - @SerialName("jwt") - val jwt: JwtFormat? = null, - ) + @Serializable(with = LimitDisclosure.LimitDisclosureSerializer::class) + enum class LimitDisclosure(val value: String) { + REQUIRED("required"), + PREFERRED("preferred"); - @Serializable - data class JwtFormat( - val alg: List - ) - } + // Custom serializer for the enum + object LimitDisclosureSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("LimitDisclosure", PrimitiveKind.STRING) - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null || this::class != other::class) { - return false + override fun serialize(encoder: Encoder, value: LimitDisclosure) { + encoder.encodeString(value.value) + } + + override fun deserialize(decoder: Decoder): LimitDisclosure { + val stringValue = decoder.decodeString() + return entries.firstOrNull { it.value == stringValue } + ?: throw SerializationException("Unknown value: $stringValue") + } + } } - other as PresentationDefinition + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (javaClass != other?.javaClass) { + return false + } + + other as Constraints + + if (fields != null) { + if (other.fields == null) { + return false + } + if (!fields.contentEquals(other.fields)) { + return false + } + } else if (other.fields != null) { + return false + } + if (limitDisclosure != other.limitDisclosure) { + return false + } - if (id != other.id) { - return false + return true } - if (!inputDescriptors.contentEquals(other.inputDescriptors)) { - return false + + override fun hashCode(): Int { + var result = fields?.contentHashCode() ?: 0 + result = 31 * result + (limitDisclosure?.hashCode() ?: 0) + return result } - return format == other.format } - override fun hashCode(): Int { - var result = id?.hashCode() ?: 0 - result = 31 * result + inputDescriptors.contentHashCode() - result = 31 * result + format.hashCode() - return result + @Serializable + data class PresentationFormat( + @SerialName("jwt") + val jwt: JwtFormat? = null, + @SerialName("sdJwt") + val sdjwt: JwtFormat? = null, + ) + + @Serializable + data class JwtFormat( + val alg: List + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || this::class != other::class) { + return false + } + + other as PresentationDefinition + + if (id != other.id) { + return false + } + if (!inputDescriptors.contentEquals(other.inputDescriptors)) { + return false } + return format == other.format + } + + override fun hashCode(): Int { + var result = id?.hashCode() ?: 0 + result = 31 * result + inputDescriptors.contentHashCode() + result = 31 * result + format.hashCode() + return result } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index 30bd30e19..365ea4449 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -65,12 +65,10 @@ data class SDJWTCredential( override var revoked: Boolean? = null override suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List): String { - var exportableKeyOption: PrivateKey? = null var disclosingClaims: List? = null for (option in options) { when (option) { - is CredentialOperationsOptions.ExportableKey -> exportableKeyOption = option.key is CredentialOperationsOptions.DisclosingClaims -> disclosingClaims = option.claims else -> {} } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index 3645d2743..0341b00e4 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -49,6 +49,7 @@ import org.hyperledger.identus.walletsdk.domain.models.DIDPair import org.hyperledger.identus.walletsdk.domain.models.DIDResolver import org.hyperledger.identus.walletsdk.domain.models.DIDUrl import org.hyperledger.identus.walletsdk.domain.models.HttpResponse +import org.hyperledger.identus.walletsdk.domain.models.InputFieldFilter import org.hyperledger.identus.walletsdk.domain.models.JWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.KeyCurve import org.hyperledger.identus.walletsdk.domain.models.Mediator @@ -83,7 +84,9 @@ import org.hyperledger.identus.walletsdk.pollux.PolluxImpl import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.CredentialRequestMeta import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -1747,6 +1750,11 @@ class EdgeAgentTests { JWTCredential.fromJwtString(jwtString).toStorableCredential() } + RestorationID.SDJWT -> { + val jwtString = it.credentialData.base64UrlDecoded + SDJWTCredential.fromSDJwtString(jwtString).toStorableCredential() + } + RestorationID.ANONCRED -> { val data = it.credentialData.base64UrlDecodedBytes PlutoRestoreTask.AnonCredentialBackUp.fromStorableData(data) @@ -1927,6 +1935,141 @@ class EdgeAgentTests { assertTrue(json.jsonObject.containsKey("presentation_definition")) } + @Test + fun `test initiatePresentationRequest SD-JWT`() = runTest { + val agent = spy( + EdgeAgent( + apollo = apolloMock, + castor = castorMock, + pluto = plutoMock, + mercury = mercuryMock, + pollux = PolluxImpl(apolloMock, castorMock), + connectionManager = connectionManagerMock, + seed = seed, + api = null, + logger = LoggerMock() + ) + ) + + val mediatorHandlerMock = mock() + `when`(connectionManagerMock.mediationHandler).thenReturn(mediatorHandlerMock) + val mediator = Mediator( + id = UUID.randomUUID().toString(), + mediatorDID = DID("did:peer:mediatordid"), + hostDID = DID("did:peer:hostdid"), + routingDID = DID("did:peer:routingdid") + ) + `when`(mediatorHandlerMock.mediator).thenReturn(mediator) + + val toDid = DID("did:peer:fdsafdsa") + val fromDid = DID("did:peer:asdf") + doReturn(fromDid).`when`(agent).createNewPeerDID(updateMediator = true) + + val vmAuthentication = DIDDocument.VerificationMethod( + id = DIDUrl(DID("2", "1", "0")), + controller = DID("2", "2", "0"), + type = Curve.ED25519.value, + publicKeyJwk = mapOf("crv" to Curve.ED25519.value, "x" to "") + ) + + val vmKeyAgreement = DIDDocument.VerificationMethod( + id = DIDUrl(DID("3", "1", "0")), + controller = DID("3", "2", "0"), + type = Curve.X25519.value, + publicKeyJwk = mapOf("crv" to Curve.X25519.value, "x" to "") + ) + + val resolverMock = mock() + val didDoc = DIDDocument( + id = DID("did:prism:asdfasdf"), + coreProperties = arrayOf( + DIDDocument.Authentication( + urls = emptyArray(), + verificationMethods = arrayOf(vmAuthentication, vmKeyAgreement) + ) + ) + ) + // Mock resolve did response + `when`(castorMock.resolveDID(any())).thenReturn(didDoc) + `when`(resolverMock.resolve(any())).thenReturn(didDoc) + + agent.initiatePresentationRequest( + type = CredentialType.SDJWT, + toDID = toDid, + presentationClaims = JWTPresentationClaims( + claims = mapOf( + "familyName" to InputFieldFilter( + type = "string", + pattern = "Wonderland" + ), + "givenName" to InputFieldFilter( + type = "string", + pattern = "Alice" + ), + "drivingClass" to InputFieldFilter( + type = "integer", + pattern = "3" + ), + "dateOfIssuance" to InputFieldFilter( + type = "string", + pattern = "2020-11-13T20:20:39+00:00" + ), + "emailAddress" to InputFieldFilter( + type = "string", + pattern = "alice@wonderland.com" + ), + "drivingLicenseID" to InputFieldFilter( + type = "string", + pattern = "12345" + ) + ) + ), + domain = "domain", + challenge = "challenge" + ) + + val captor = argumentCaptor() + verify(connectionManagerMock).sendMessage(captor.capture()) + val sentMessage = captor.firstValue + assertEquals(fromDid.toString(), sentMessage.from.toString()) + assertEquals(toDid.toString(), sentMessage.to.toString()) + assertEquals(1, sentMessage.attachments.size) + assertTrue(sentMessage.attachments.first().data::class == AttachmentBase64::class) + val json = sentMessage.attachments.first().data.getDataAsJsonString() + val presentationDefinition = Json.decodeFromString(json) + assertNotNull(presentationDefinition.presentationDefinition.format.sdjwt) + } + + @Test + fun `test handlePresentationDefinitionRequest`() = runTest { + val agent = spy( + EdgeAgent( + apollo = apolloMock, + castor = castorMock, + pluto = plutoMock, + mercury = mercuryMock, + pollux = PolluxImpl(apolloMock, castorMock), + connectionManager = connectionManagerMock, + seed = seed, + api = null, + logger = LoggerMock() + ) + ) + + val credential = JWTCredential.fromJwtString( + "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MjU3MTlhOTZiMTUxMjA3MTY5ODFhODQzMGFkMGNiOTY4ZGQ1MzQwNzM1OTNjOGNkM2YxZDI3YTY4MDRlYzUwZTpDcG9DQ3BjQ0Vsb0tCV3RsZVMweEVBSkNUd29KYzJWamNESTFObXN4RWlBRW9TQ241dHlEYTZZNnItSW1TcXBKOFkxbWo3SkMzX29VekUwTnl5RWlDQm9nc2dOYWVSZGNDUkdQbGU4MlZ2OXRKZk53bDZyZzZWY2hSM09xaGlWYlRhOFNXd29HWVhWMGFDMHhFQVJDVHdvSmMyVmpjREkxTm1zeEVpRE1rQmQ2RnRpb0prM1hPRnUtX2N5NVhtUi00dFVRMk5MR2lXOGFJU29ta1JvZzZTZGU5UHduRzBRMFNCVG1GU1REYlNLQnZJVjZDVExYcmpJSnR0ZUdJbUFTWEFvSGJXRnpkR1Z5TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTzcxMG10MVdfaXhEeVFNM3hJczdUcGpMQ05PRFF4Z1ZoeDVzaGZLTlgxb2FJSFdQcnc3SVVLbGZpYlF0eDZKazRUU2pnY1dOT2ZjT3RVOUQ5UHVaN1Q5dCIsInN1YiI6ImRpZDpwcmlzbTpiZWVhNTIzNGFmNDY4MDQ3MTRkOGVhOGVjNzdiNjZjYzdmM2U4MTVjNjhhYmI0NzVmMjU0Y2Y5YzMwNjI2NzYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklnZVNnLTJPTzFKZG5welVPQml0eklpY1hkZnplQWNUZldBTi1ZQ2V1Q2J5SWFJSlE0R1RJMzB0YVZpd2NoVDNlMG5MWEJTNDNCNGo5amxzbEtvMlpsZFh6akVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lIa29QdGpqdFNYWjZjMURnWXJjeUluRjNYODNnSEUzMWdEZm1BbnJnbThpR2lDVU9Ca3lOOUxXbFlzSElVOTN0Snkxd1V1TndlSV9ZNWJKU3FObVpYVjg0dyIsIm5iZiI6MTY4NTYzMTk5NSwiZXhwIjoxNjg1NjM1NTk1LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsUHJvcDIiOiJUZXN0MyIsImlkIjoiZGlkOnByaXNtOmJlZWE1MjM0YWY0NjgwNDcxNGQ4ZWE4ZWM3N2I2NmNjN2YzZTgxNWM2OGFiYjQ3NWYyNTRjZjljMzA2MjY3NjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdlU2ctMk9PMUpkbnB6VU9CaXR6SWljWGRmemVBY1RmV0FOLVlDZXVDYnlJYUlKUTRHVEkzMHRhVml3Y2hUM2UwbkxYQlM0M0I0ajlqbHNsS28yWmxkWHpqRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUhrb1B0amp0U1haNmMxRGdZcmN5SW5GM1g4M2dIRTMxZ0RmbUFucmdtOGlHaUNVT0JreU45TFdsWXNISVU5M3RKeTF3VXVOd2VJX1k1YkpTcU5tWlhWODR3In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.x0SF17Y0VCDmt7HceOdTxfHlofsZmY18Rn6VQb0-r-k_Bm3hTi1-k2vkdjB25hdxyTCvxam-AkAP-Ag3Ahn5Ng" + ) + + val msg = Json.decodeFromString( + """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjI0YTdlNWU4LWQ3YjQtNDUxYy1hOThkLTA3ZDY4NjVhMzQwYSIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjNmMWYzYTliLThjZDUtNDZkMS04Y2E0LTBlYzYyN2YxZTdmMiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiXSwiaWQiOiI5MjM3ZmNhMy1lZjcyLTQyOGEtYjIyYy02YzVmZTZmZTU0NWIiLCJuYW1lIjoiZmFtaWx5TmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZ2l2ZW5OYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5naXZlbk5hbWUiXSwiaWQiOiI3MmQ4NmYwMS04NGYzLTRhNDYtOGFiOC1hN2I0OGE5YjU2MTAiLCJuYW1lIjoiZ2l2ZW5OYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiQWxpY2UifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZHJpdmluZ0NsYXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nQ2xhc3MiXSwiaWQiOiIyNmI3ZmY3Zi1kOTcyLTQxZGYtYTNkZC0zYmE3YzhiNDAwOWEiLCJuYW1lIjoiZHJpdmluZ0NsYXNzIiwiZmlsdGVyIjp7InR5cGUiOiJpbnRlZ2VyIiwicGF0dGVybiI6IjMifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZGF0ZU9mSXNzdWFuY2UiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmRhdGVPZklzc3VhbmNlIl0sImlkIjoiYWZiZmI1NWMtMWY5Ni00ODlkLWJmOGUtYzZhNTUxYjg3ODFjIiwibmFtZSI6ImRhdGVPZklzc3VhbmNlIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiMjAyMC0xMS0xM1QyMDoyMDozOSswMDowMCJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyJdLCJpZCI6IjAyZGIxOTA0LWY3NTMtNDI2NC04ZTA0LWI5NGMxNWFkYzA3MyIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJhbGljZUB3b25kZXJsYW5kLmNvbSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIl0sImlkIjoiODgzYThkMzYtNWYzMy00OGVjLWJhYjktNzE0MGRiYWMyYTVmIiwibmFtZSI6ImRyaXZpbmdMaWNlbnNlSUQiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiIxMjM0NSJ9fV0sImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCJ9fV0sImZvcm1hdCI6eyJzZEp3dCI6eyJhbGciOlsiRVMyNTZrIl19fX0sIm9wdGlvbnMiOnsicHJlc2VudGF0aW9uRnJhbWUiOnt9fX0"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" + ) + + agent.preparePresentationForRequestProof( + request = RequestPresentation.fromMessage(msg), + credential = credential + ) + } + val getCredentialDefinitionResponse = "{\"schemaId\":\"http://host.docker.internal:8000/prism-agent/schema-registry/schemas/5e0d5a93-4bfd-3111-a956-5d5bc82f76cc\",\"type\":\"CL\",\"tag\":\"licence\",\"value\":{\"primary\":{\"n\":\"105195159277979097653318357586659371305119697478469834190626350283715795188687389523188659352120689851168860621983864738336838773213022505168653440146374011050277159372491059901432822905781969400722059341786498751125483895348734607382548396665339315322605154516776326303787844694026898270194867398625429469096229269732265502538641116512214652017416624138065704599041020588805936844771273861390913500753293895219370960892829297672575154196820931047049021760519166121287056337193413235473255257349024671869248216238831094979209384406168241010010012567685965827447177652200129684927663161550376084422586141212281146491949\",\"s\":\"85376740935726732134199731472843597191822272986425414914465211197069650618238336366149699822721009443794877925725075553195071288777117865451699414058058985000654277974066307286552934230286237253977472401290858765904161191229985245519871949378628131263513153683765553672655918133136828182050729012388157183851720391379381006921499997765191873729408614024320763554099291141052786589157823043612948619201525441997065264492145372001259366749278235381762443117203343617927241093647322654346302447381494008414208398219626199373278313446814209403507903682881070548386699522575055488393512785511441688197244526708647113340516\",\"r\":{\"dateofissuance\":\"16159515692057558658031632775257139859912833740243870833808276956469677196577164655991169139545328065546186056342530531355718904597216453319851305621683589202769847381737819412615902541110462703838858425423753481085962114120185123089078513531045426316918036549403698066078445947881055316312848598741184161901260446303171175343050250045452903485086185722998336149005743485268486377824763449026501058416292877646187105446333888525480394665310217044483841168928926515929150167890936706159800372381200383816724043496032886366767166850459338411710056171379538841845247931898550165532492578625954615979453881721709564750235\",\"drivingclass\":\"83649701835078373520097916558245060224505938113940626586910000950978790663411517512280043632278010831292224659523658613504637416710001103641231226266903556936380105758523760424939825687213460920436570466066231912959327201876189240504388424799892400351592593406285436824571943165913587899115814843543998396726679289422080229750418336051741708013580146373647528674381958028243228435161765957312248113519708734663989428761879029086059388435772829434952754093999424834120341657211221855300108096057633128467059590470639772605075954658131680801785637700237403873940041665483384938586320674338994185073499523485570537331062\",\"emailaddress\":\"96995643129591814391344614133120459563648002327749700279517548454036811217735867585059116635583558148259032071807493674533230465312311981127622542797279917256478867847832932893748528200469349058284133058865149153179959849308383505167342565738382180666525211256221655129861213392455759272915565057394420728271409215556596974900718332893753172173500744392522771654048192448229319313386967045678744665093451560743782910263014930200762027209565313884859542996067229707388839912195826334964819133016500346618083969320902775088800287566711941842968839787149808739739233388585677095545116231323172342995837636586249573194609\",\"drivinglicenseid\":\"102840929811153624977554462471309185033977661854754815794111114507549576719389525167082631547450413573293352276930065480432301200611396989595571202142654033217842162456070556560693402484110499573693863745648118310258284468114751958738878996458420605301017450868522680454545537837403398645500541915771765220093329728663621098538954397330411649083351383375839056527007892276284168437065687748085384178113959961057476582871100422859953560730152958588610850909069434658487744782540788968302663076149478487413357533660817020800754493642858564081116318655661240523146995256712471572605700346459123074377380656921337264554594\",\"familyname\":\"2428690037146701497427424649573806616639612325136606164619283916796880313617677563507218774958436668407050506838114136163250163675016510113975582318007560622124292458766639319715064358235569650961433812439763343736699708535945693241909905707497180931492818502593885932421170612418693515054756633264933222189766691632082890045477718331705366111669009551578289182848340651375008362238266590844461708981816856194045325523248527964502118319210042254240848590574645476930113881493472578612352948284862674703949781070309344526122291448990325949065193279599181502524961004046979227803224474342778516917124487012958845744311\",\"master_secret\":\"96236339155824229583363924057798366491998077727991424922911165403434522806469328114407334094535810942859512352089785125683335350062474092708044674085769524387654467267128528564551803293661877480971961092735622606052503557881856409855812611523475975566606131897917979412576797874632169829901968854843162299366867885636535326810998541141840561418097240137120398317445832694001031827068485975315937269024666370665530455146256019590700349556357390218401217383173228376078058967743472704019765210324846681867991543267171763037513180046865961560351035005185946817643006206395175857900512245900162751815626427008481585714891\"},\"rctxt\":\"54359809198312125478916383106913469635175253891208897419510030559787479974126666313900084654632259260010008369569778456071591398552341004538623276997178295939490854663263886825856426285604332554317424030793691008221895556474599466123873279022389276698551452690414982831059651505731449763128921782866843113361548859434294057249048041670761184683271568216202174527891374770703485794299697663353847310928998125365841476766767508733046891626759537001358973715760759776149482147060701775948253839125589216812475133616408444838011643485797584321993661048373877626880635937563283836661934456534313802815974883441215836680800\",\"z\":\"99592262675748359673042256590146366586480829950402370244401571195191609039150608482506917768910598228167758026656953725016982562881531475875469671976107506976812319765644401707559997823702387678953647104105378063905395973550729717937712350758544336716556268064226491839700352305793370980462034813589488455836259737325502578253339820590260554457468082536249525493340350556649403477875367398139579018197084796440810685458274393317299082017275568964540311198115802021902455672385575542594821996060452628805634468222196284384514736044680778624637228114693554834388824212714580770066729185685978935409859595244639193538156\"}},\"issuerId\":\"did:prism:604ba1764ab89993f9a74625cc4f3e04737919639293eb382cc7adc53767f550\"}" diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pluto/BackupRestorationTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pluto/BackupRestorationTests.kt index ad678b453..b46b5ea03 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pluto/BackupRestorationTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pluto/BackupRestorationTests.kt @@ -36,6 +36,7 @@ import org.hyperledger.identus.walletsdk.logger.LoggerMock import org.hyperledger.identus.walletsdk.pluto.models.backup.BackupV0_0_1 import org.hyperledger.identus.walletsdk.pollux.PolluxImpl import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -219,6 +220,11 @@ class BackupRestorationTests { JWTCredential.fromJwtString(jwtString).toStorableCredential() } + RestorationID.SDJWT -> { + val jwtString = it.credentialData.base64UrlDecoded + SDJWTCredential.fromSDJwtString(jwtString).toStorableCredential() + } + RestorationID.ANONCRED -> { val data = it.credentialData.base64UrlDecodedBytes PlutoRestoreTask.AnonCredentialBackUp.fromStorableData(data) diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 037aec172..dd1477eaf 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -53,6 +53,7 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.AnoncredsPresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.JWTPresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefinitionRequest @@ -1182,7 +1183,52 @@ class PolluxImplTest { val json = Json.parseToJsonElement(header) assertTrue(json.jsonObject.containsKey("kid")) val kid = json.jsonObject["kid"]!!.jsonPrimitive.content - assertEquals("did:prism:cd6cf9f94a43c53e286b0f2015c0083701350a694f52a22ee02e3bd29d93eba9:CrQBCrEBEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xJDCg9hdXRoZW50aWNhdGlvbjAQBEouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xotCgojZGlkY29tbS0xEhBESURDb21tTWVzc2FnaW5nGg1kaWQ6cGVlcjp0ZXN0#authentication0", kid) + assertEquals( + "did:prism:cd6cf9f94a43c53e286b0f2015c0083701350a694f52a22ee02e3bd29d93eba9:CrQBCrEBEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xJDCg9hdXRoZW50aWNhdGlvbjAQBEouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xotCgojZGlkY29tbS0xEhBESURDb21tTWVzc2FnaW5nGg1kaWQ6cGVlcjp0ZXN0#authentication0", + kid + ) + } + + @Test + fun `Test createPresentationDefinitionRequest`() = runTest { + pollux = PolluxImpl(apollo, castor, api) + + val sdJwtPresentationDefinitionRequest = pollux.createPresentationDefinitionRequest( + type = CredentialType.SDJWT, + presentationClaims = JWTPresentationClaims( + claims = mapOf( + "familyName" to InputFieldFilter( + type = "string", + pattern = "Wonderland" + ), + "givenName" to InputFieldFilter( + type = "string", + pattern = "Alice" + ), + "drivingClass" to InputFieldFilter( + type = "integer", + pattern = "3" + ), + "dateOfIssuance" to InputFieldFilter( + type = "string", + pattern = "2020-11-13T20:20:39+00:00" + ), + "emailAddress" to InputFieldFilter( + type = "string", + pattern = "alice@wonderland.com" + ), + "drivingLicenseID" to InputFieldFilter( + type = "string", + pattern = "12345" + ) + ) + ), + options = SDJWTPresentationOptions( + presentationFrame = emptyMap() + ) + ) + + // TODO: Validate SD-JWT specific fields } private suspend fun createVerificationTestCase(testCaseOptions: VerificationTestCase): Triple { diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index babc14111..65078ba41 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -119,9 +119,29 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application toDID = DID(toDID), presentationClaims = JWTPresentationClaims( claims = mapOf( + "familyName" to InputFieldFilter( + type = "string", + pattern = "Wonderland" + ), + "givenName" to InputFieldFilter( + type = "string", + pattern = "Alice" + ), + "drivingClass" to InputFieldFilter( + type = "integer", + pattern = "3" + ), + "dateOfIssuance" to InputFieldFilter( + type = "string", + pattern = "2020-11-13T20:20:39+00:00" + ), "emailAddress" to InputFieldFilter( type = "string", - pattern = "test@iohk.io" + pattern = "alice@wonderland.com" + ), + "drivingLicenseID" to InputFieldFilter( + type = "string", + pattern = "12345" ) ) ), From 38a2fc6d5f1ae5b852f05ee166a3be6faa605a9e Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 19 Sep 2024 20:55:40 -0400 Subject: [PATCH 06/22] feat: SD-JWT verification Signed-off-by: Cristian G --- .../domain/models/VerifiableCredential.kt | 5 +++ .../identus/walletsdk/edgeagent/EdgeAgent.kt | 3 +- .../walletsdk/edgeagent/EdgeAgentTests.kt | 31 ++++++++++++++++--- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt index 3574cae20..01a14380f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt @@ -112,6 +112,11 @@ data class JWTPresentationClaims( val claims: Map ) : PresentationClaims +data class SDJWTPresentationClaims( + val claims: Map, + val presentationFrame: Map +) : PresentationClaims + data class AnoncredsPresentationClaims( val predicates: Map, val attributes: Map diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index bc7ba587b..63c320559 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -92,6 +92,7 @@ import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.PresentationClaims import org.hyperledger.identus.walletsdk.domain.models.PrismDIDInfo import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential +import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.Signature import org.hyperledger.identus.walletsdk.domain.models.UnknownError @@ -1195,7 +1196,7 @@ open class EdgeAgent { } else { SDJWTPresentationOptions( sdjwt = arrayOf("ES256k"), - presentationFrame = emptyMap() + presentationFrame = (presentationClaims as SDJWTPresentationClaims).presentationFrame ) } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index 0341b00e4..d4ce489bd 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -57,6 +57,7 @@ import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential import org.hyperledger.identus.walletsdk.domain.models.RequestedAttributes +import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.Signature import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurveKey @@ -1996,7 +1997,7 @@ class EdgeAgentTests { agent.initiatePresentationRequest( type = CredentialType.SDJWT, toDID = toDid, - presentationClaims = JWTPresentationClaims( + presentationClaims = SDJWTPresentationClaims( claims = mapOf( "familyName" to InputFieldFilter( type = "string", @@ -2022,7 +2023,8 @@ class EdgeAgentTests { type = "string", pattern = "12345" ) - ) + ), + presentationFrame = mapOf("email" to true) ), domain = "domain", challenge = "challenge" @@ -2056,8 +2058,29 @@ class EdgeAgentTests { ) ) - val credential = JWTCredential.fromJwtString( - "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MjU3MTlhOTZiMTUxMjA3MTY5ODFhODQzMGFkMGNiOTY4ZGQ1MzQwNzM1OTNjOGNkM2YxZDI3YTY4MDRlYzUwZTpDcG9DQ3BjQ0Vsb0tCV3RsZVMweEVBSkNUd29KYzJWamNESTFObXN4RWlBRW9TQ241dHlEYTZZNnItSW1TcXBKOFkxbWo3SkMzX29VekUwTnl5RWlDQm9nc2dOYWVSZGNDUkdQbGU4MlZ2OXRKZk53bDZyZzZWY2hSM09xaGlWYlRhOFNXd29HWVhWMGFDMHhFQVJDVHdvSmMyVmpjREkxTm1zeEVpRE1rQmQ2RnRpb0prM1hPRnUtX2N5NVhtUi00dFVRMk5MR2lXOGFJU29ta1JvZzZTZGU5UHduRzBRMFNCVG1GU1REYlNLQnZJVjZDVExYcmpJSnR0ZUdJbUFTWEFvSGJXRnpkR1Z5TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTzcxMG10MVdfaXhEeVFNM3hJczdUcGpMQ05PRFF4Z1ZoeDVzaGZLTlgxb2FJSFdQcnc3SVVLbGZpYlF0eDZKazRUU2pnY1dOT2ZjT3RVOUQ5UHVaN1Q5dCIsInN1YiI6ImRpZDpwcmlzbTpiZWVhNTIzNGFmNDY4MDQ3MTRkOGVhOGVjNzdiNjZjYzdmM2U4MTVjNjhhYmI0NzVmMjU0Y2Y5YzMwNjI2NzYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklnZVNnLTJPTzFKZG5welVPQml0eklpY1hkZnplQWNUZldBTi1ZQ2V1Q2J5SWFJSlE0R1RJMzB0YVZpd2NoVDNlMG5MWEJTNDNCNGo5amxzbEtvMlpsZFh6akVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lIa29QdGpqdFNYWjZjMURnWXJjeUluRjNYODNnSEUzMWdEZm1BbnJnbThpR2lDVU9Ca3lOOUxXbFlzSElVOTN0Snkxd1V1TndlSV9ZNWJKU3FObVpYVjg0dyIsIm5iZiI6MTY4NTYzMTk5NSwiZXhwIjoxNjg1NjM1NTk1LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsUHJvcDIiOiJUZXN0MyIsImlkIjoiZGlkOnByaXNtOmJlZWE1MjM0YWY0NjgwNDcxNGQ4ZWE4ZWM3N2I2NmNjN2YzZTgxNWM2OGFiYjQ3NWYyNTRjZjljMzA2MjY3NjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdlU2ctMk9PMUpkbnB6VU9CaXR6SWljWGRmemVBY1RmV0FOLVlDZXVDYnlJYUlKUTRHVEkzMHRhVml3Y2hUM2UwbkxYQlM0M0I0ajlqbHNsS28yWmxkWHpqRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUhrb1B0amp0U1haNmMxRGdZcmN5SW5GM1g4M2dIRTMxZ0RmbUFucmdtOGlHaUNVT0JreU45TFdsWXNISVU5M3RKeTF3VXVOd2VJX1k1YkpTcU5tWlhWODR3In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.x0SF17Y0VCDmt7HceOdTxfHlofsZmY18Rn6VQb0-r-k_Bm3hTi1-k2vkdjB25hdxyTCvxam-AkAP-Ag3Ahn5Ng" + val privateKey = Secp256k1KeyPair.generateKeyPair( + seed = seed, + curve = KeyCurve(Curve.SECP256K1) + ).privateKey + val storablePrivateKeys = listOf( + StorablePrivateKey( + id = UUID.randomUUID().toString(), + restorationIdentifier = "secp256k1+priv", + data = privateKey.raw.base64UrlEncoded, + keyPathIndex = 0 + ) + ) + // Mock getDIDPrivateKeysByDID response + `when`(plutoMock.getDIDPrivateKeysByDID(any())).thenReturn(flow { emit(storablePrivateKeys) }) + `when`( + apolloMock.restorePrivateKey( + storablePrivateKeys.first().restorationIdentifier, + storablePrivateKeys.first().data + ) + ).thenReturn(privateKey) + + val credential = SDJWTCredential.fromSDJwtString( + "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJkaWQ6cHJpc206NmYyM2RkYWNlNTE5YjY4ZGZjMGZhMDZlOTkyZGI0MGYyZjNjNTg0YWYzODJjZTQ0NmZhMmZkMGUwNDJlNWRlYTpDb1VCQ29JQkVqc0tCMjFoYzNSbGNqQVFBVW91Q2dselpXTndNalUyYXpFU0lRTWNLd0VpdEdiUUt0R2EtakZYaTNtMXU3T1AySk11a1lYUW5aazNmUUlYdnhKRENnOWhkWFJvWlc1MGFXTmhkR2x2YmpBUUJFb3VDZ2x6WldOd01qVTJhekVTSVFNY0t3RWl0R2JRS3RHYS1qRlhpM20xdTdPUDJKTXVrWVhRblprM2ZRSVh2dyIsImVtYWlsQWRkcmVzcyI6eyJfc2QiOlsiVDFQbWo5XzVvSzl0UGQyX1h1MUI2bkpmMTR6UlpXWGdySmhnVkktbHV1OCJdfSwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJkaWQ6cHJpc206Y2UzNDAzYjVhNzMzODgzMDM1ZDZlYzQzYmEwNzVhNDFjOWNjMGEzMjU3OTc3ZDgwYzc1ZDYzMTlhZGUwZWQ3MCIsImV4cCI6MTczNTY4OTY2MSwiaWF0IjoxNTE2MjM5MDIyfQ._sPrEKiMwJg5e6c24dgCXBH2FZ3tzZL-6llwGj9Rt_XQo1ABwJWB6Rdr_UO3ow3ooAdsIedy6Gau9NYbWv0fDw~WyJZMGlWS3BYYnBWdnpiang4Z2J4cUhnIiwiZW1haWxBZGRyZXNzIiwidGVzdEBpb2hrLmlvIl0~" ) val msg = Json.decodeFromString( From 4b77970d491c8f55aa115e3a59ddaec2e66b0b45 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Tue, 24 Sep 2024 16:36:29 -0400 Subject: [PATCH 07/22] feat: SD-JWT verification ready to test Signed-off-by: Cristian G --- .../domain/models/ProvableCredential.kt | 3 +- .../domain/models/VerifiableCredential.kt | 13 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 97 ++- .../protocols/pickup/PickupRunner.kt | 7 +- .../JWTPresentationOptions.kt | 5 +- .../PreparePresentationOptions.kt | 7 + .../PresentationSubmissionOptions.kt | 4 + .../walletsdk/pollux/DescriptorPath.kt | 14 +- .../identus/walletsdk/pollux/PolluxImpl.kt | 688 ++++++++++++------ .../models/PresentationDefinitionRequest.kt | 11 +- .../pollux/models/PresentationSubmission.kt | 3 +- .../pollux/models/SDJWTCredential.kt | 66 +- .../walletsdk/edgeagent/EdgeAgentTests.kt | 94 ++- .../walletsdk/pollux/DescriptorPathTest.kt | 37 + .../walletsdk/pollux/PolluxImplTest.kt | 41 +- .../pollux/models/SDJWTCredentialTest.kt | 104 +++ .../ui/credentials/CredentialsViewModel.kt | 2 +- .../ui/messages/MessagesViewModel.kt | 7 +- 18 files changed, 867 insertions(+), 336 deletions(-) create mode 100644 edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt create mode 100644 edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt create mode 100644 edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredentialTest.kt diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt index bd13065bf..5b20aa7ba 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt @@ -1,6 +1,7 @@ package org.hyperledger.identus.walletsdk.domain.models import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey sealed class CredentialOperationsOptions { data class Schema(val id: String, val json: String) : CredentialOperationsOptions() @@ -10,7 +11,7 @@ sealed class CredentialOperationsOptions { data class LinkSecret(val id: String, val secret: String) : CredentialOperationsOptions() data class SubjectDID(val did: DID) : CredentialOperationsOptions() data class Entropy(val entropy: String) : CredentialOperationsOptions() - data class SignableKey(val key: SignableKey?) : CredentialOperationsOptions() + data class SignableKey(val key: org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey?) : CredentialOperationsOptions() data class ExportableKey(val key: PrivateKey?) : CredentialOperationsOptions() data class ZkpPresentationParams(val attributes: Map, val predicates: List) : CredentialOperationsOptions() diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt index 01a14380f..2591c8dce 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/VerifiableCredential.kt @@ -104,22 +104,25 @@ sealed interface VerifiableCredential { } } -sealed interface PresentationClaims +sealed interface PresentationClaims { + val claims: Map +} data class JWTPresentationClaims( val schema: String? = null, val issuer: String? = null, - val claims: Map + override val claims: Map ) : PresentationClaims data class SDJWTPresentationClaims( - val claims: Map, - val presentationFrame: Map + val issuer: String? = null, + override val claims: Map ) : PresentationClaims data class AnoncredsPresentationClaims( val predicates: Map, - val attributes: Map + val attributes: Map, + override val claims: Map = emptyMap() ) : PresentationClaims @OptIn(ExperimentalSerializationApi::class) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 63c320559..7b92cdd6c 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -13,13 +13,19 @@ import com.nimbusds.jose.JWEHeader import com.nimbusds.jose.JWEObject import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.Payload +import com.nimbusds.jose.crypto.ECDSAVerifier import com.nimbusds.jose.crypto.Ed25519Signer import com.nimbusds.jose.crypto.X25519Decrypter import com.nimbusds.jose.crypto.X25519Encrypter import com.nimbusds.jose.jwk.KeyUse import com.nimbusds.jose.jwk.OctetKeyPair +import com.nimbusds.jose.jwk.gen.ECKeyGenerator import com.nimbusds.jose.util.Base64URL +import eu.europa.ec.eudi.sdjwt.JwtAndClaims +import eu.europa.ec.eudi.sdjwt.SdJwt import eu.europa.ec.eudi.sdjwt.SdJwtIssuer +import eu.europa.ec.eudi.sdjwt.SdJwtVerifier +import eu.europa.ec.eudi.sdjwt.asJwtVerifier import eu.europa.ec.eudi.sdjwt.exp import eu.europa.ec.eudi.sdjwt.iat import eu.europa.ec.eudi.sdjwt.iss @@ -28,7 +34,6 @@ import eu.europa.ec.eudi.sdjwt.plain import eu.europa.ec.eudi.sdjwt.sd import eu.europa.ec.eudi.sdjwt.sdJwt import eu.europa.ec.eudi.sdjwt.serialize -import eu.europa.ec.eudi.sdjwt.structured import eu.europa.ec.eudi.sdjwt.sub import eu.europa.ec.eudi.sdjwt.vc.SD_JWT_VC_TYPE import io.ktor.client.plugins.contentnegotiation.ContentNegotiation @@ -39,6 +44,8 @@ import io.ktor.serialization.kotlinx.json.json import io.ktor.util.date.getTimeMillis import java.net.UnknownHostException import java.security.SecureRandom +import java.security.interfaces.ECPrivateKey +import java.security.interfaces.ECPublicKey import java.util.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -49,6 +56,7 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.datetime.Instant import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -57,8 +65,8 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.put import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey @@ -121,15 +129,17 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.outOfBand.OutOfBand import org.hyperledger.identus.walletsdk.edgeagent.protocols.outOfBand.PrismOnboardingInvitation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.AnoncredsPresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.JWTPresentationOptions +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PreparePresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.Presentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsAnoncreds import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.RequestPresentation +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPreparePresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.LogComponent -import org.hyperledger.identus.walletsdk.logger.Metadata import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl +import org.hyperledger.identus.walletsdk.logger.Metadata import org.hyperledger.identus.walletsdk.pluto.PlutoBackupTask import org.hyperledger.identus.walletsdk.pluto.PlutoRestoreTask import org.hyperledger.identus.walletsdk.pluto.models.backup.BackupV0_0_1 @@ -138,7 +148,6 @@ import org.hyperledger.identus.walletsdk.pollux.models.CredentialRequestMeta import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential -import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest import org.kotlincrypto.hash.sha2.SHA256 /** @@ -1057,7 +1066,8 @@ open class EdgeAgent { @Throws(PolluxError.InvalidPrismDID::class, EdgeAgentError.CredentialNotValidForPresentationRequest::class) suspend fun preparePresentationForRequestProof( request: RequestPresentation, - credential: T + credential: T, + preparePresentationOptions: PreparePresentationOptions? = null ): Presentation where T : Credential, T : ProvableCredential { val attachmentFormat = request.attachments.first().format ?: CredentialType.Unknown.type // Presentation request from agent @@ -1066,7 +1076,7 @@ open class EdgeAgent { when (attachmentFormat) { CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type -> { // Presentation Exchange - return handlePresentationDefinitionRequest(request, credential) + return handlePresentationDefinitionRequest(request, credential, preparePresentationOptions) } CredentialType.ANONCREDS_PROOF_REQUEST.type -> { @@ -1126,14 +1136,32 @@ open class EdgeAgent { } CredentialType.SDJWT.type -> { + if (preparePresentationOptions == null) { + throw EdgeAgentError.MissingOrNullFieldError( + "presentationFrame", + "preparePresentationOptions" + ) + } + + if (preparePresentationOptions !is SDJWTPreparePresentationOptions) { + throw Exception() // TODO: Custom exception + } + val requestData = request.attachments.firstNotNullOf { it.data.getDataAsJsonString() } + + val disclosingClaims = mutableListOf() + preparePresentationOptions.presentationFrame.forEach { presentationFrame -> + if (presentationFrame.value) { + disclosingClaims.add(presentationFrame.key) + } + } try { presentationString = credential.presentation( attachmentFormat, requestData.encodeToByteArray(), - listOf(CredentialOperationsOptions.DisclosingClaims(listOf(credential.claims.toString()))) + listOf(CredentialOperationsOptions.DisclosingClaims(disclosingClaims)) ) } catch (e: Exception) { throw EdgeAgentError.CredentialNotValidForPresentationRequest() @@ -1195,8 +1223,7 @@ open class EdgeAgent { ) } else { SDJWTPresentationOptions( - sdjwt = arrayOf("ES256k"), - presentationFrame = (presentationClaims as SDJWTPresentationClaims).presentationFrame + sdjwt = arrayOf("ES256k") ) } @@ -1248,7 +1275,8 @@ open class EdgeAgent { private suspend fun handlePresentationDefinitionRequest( requestPresentation: RequestPresentation, - credential: Credential + credential: Credential, + preparePresentationOptions: PreparePresentationOptions? = null ): Presentation { try { if (credential !is ProvableCredential) { @@ -1306,6 +1334,17 @@ open class EdgeAgent { throw EdgeAgentError.CredentialNotValidForPresentationRequest() } + if (preparePresentationOptions == null) { + throw EdgeAgentError.MissingOrNullFieldError( + "presentationFrame", + "preparePresentationOptions" + ) + } + + if (preparePresentationOptions !is SDJWTPreparePresentationOptions) { + throw Exception() // TODO: Custom exception + } + val didString = credential.subject ?: throw Exception("Credential must contain subject") @@ -1314,23 +1353,19 @@ open class EdgeAgent { val privateKey = apollo.restorePrivateKey(storablePrivateKey.restorationIdentifier, storablePrivateKey.data) - val presentationDefinitionRequest = Json.decodeFromString( - presentationDefinitionRequestString - ) - - val disclosingClaims = presentationDefinitionRequest.options?.let { options -> - options.presentationFrame.map { presentationFrame -> - presentationFrame.key + val disclosingClaims = mutableListOf() + preparePresentationOptions.presentationFrame.forEach { presentationFrame -> + if (presentationFrame.value) { + disclosingClaims.add(presentationFrame.key) } - } ?: emptyList() + } val presentationString = credential.presentation( CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, presentationDefinitionRequestString.encodeToByteArray(), listOf( CredentialOperationsOptions.DisclosingClaims(disclosingClaims), - CredentialOperationsOptions.SubjectDID(DID(didString)), - CredentialOperationsOptions.ExportableKey(privateKey) + CredentialOperationsOptions.SubjectDID(DID(didString)) ) ) @@ -1561,11 +1596,9 @@ open class EdgeAgent { } } - suspend fun createSDJWTCredential(): SDJWTCredential { - val keyPair = Ed25519KeyPair.generateKeyPair() - + // TODO: Remove before merging + suspend fun createSDJWTCredential(keyPair: Ed25519KeyPair): SDJWTCredential { val subject = createNewPrismDID() - println(subject.toString()) val octet = OctetKeyPair.Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(keyPair.publicKey.raw)) .d(Base64URL.encode(keyPair.privateKey.raw)) @@ -1585,13 +1618,21 @@ open class EdgeAgent { iat(1516239022) exp(1735689661) } - structured("emailAddress") { - sd { - put("emailAddress", "test@iohk.io") - } + sd { + put("first_name", "Cristian") + put("last_name", "Gonzalez") + put("emailAddress", "test@iohk.io") } } ).getOrThrow().serialize() + + val verified: SdJwt.Issuance = runBlocking { + SdJwtVerifier.verifyIssuance( + jwtSignatureVerifier = ECDSAVerifier(keyPair.publicKey.jca() as ECPublicKey).asJwtVerifier(), + unverifiedSdJwt = sdjwt + ).getOrThrow() + } + val cred = SDJWTCredential.fromSDJwtString(sdjwt) pluto.storeCredential(cred.toStorableCredential()) return cred diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/pickup/PickupRunner.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/pickup/PickupRunner.kt index 16ea76157..1e642130f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/pickup/PickupRunner.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/pickup/PickupRunner.kt @@ -24,7 +24,8 @@ class PickupRunner(message: Message, private val mercury: Mercury) { */ enum class PickupResponseType(val type: String) { STATUS("status"), - DELIVERY("delivery") + DELIVERY("delivery"), + PROBLEM_REPORT("problem_report") } /** @@ -60,6 +61,10 @@ class PickupRunner(message: Message, private val mercury: Mercury) { this.message = PickupResponse(PickupResponseType.DELIVERY, message) } + ProtocolType.ProblemReport.value -> { + this.message = PickupResponse(PickupResponseType.PROBLEM_REPORT, message) + } + else -> { throw EdgeAgentError.InvalidMessageType( type = message.piuri, diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt index 4d55f28c7..43100c8f8 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/JWTPresentationOptions.kt @@ -45,8 +45,7 @@ data class JWTPresentationOptions( data class SDJWTPresentationOptions( override val name: String? = "Presentation", override val purpose: String = "Presentation definition", - val sdjwt: Array = arrayOf("ES256K"), - val presentationFrame: Map + val sdjwt: Array = arrayOf("ES256K") ) : PresentationOptions { override val type: CredentialType = CredentialType.SDJWT @@ -58,7 +57,6 @@ data class SDJWTPresentationOptions( if (name != other.name) return false if (purpose != other.purpose) return false - if (presentationFrame != other.presentationFrame) return false return sdjwt.contentEquals(other.sdjwt) } @@ -66,7 +64,6 @@ data class SDJWTPresentationOptions( var result = name?.hashCode() ?: 0 result = 31 * result + purpose.hashCode() result = 31 * result + (sdjwt.contentHashCode()) - result = 31 * result + presentationFrame.hashCode() return result } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt new file mode 100644 index 000000000..4f992946e --- /dev/null +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt @@ -0,0 +1,7 @@ +package org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation + +sealed interface PreparePresentationOptions + +data class SDJWTPreparePresentationOptions( + val presentationFrame: Map +) : PreparePresentationOptions \ No newline at end of file diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PresentationSubmissionOptions.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PresentationSubmissionOptions.kt index 5c3fb18b0..b9f54043f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PresentationSubmissionOptions.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PresentationSubmissionOptions.kt @@ -6,6 +6,10 @@ data class PresentationSubmissionOptionsJWT( val presentationDefinitionRequest: String ) : PresentationSubmissionOptions +data class PresentationSubmissionOptionsSDJWT( + val presentationDefinitionRequest: String +) : PresentationSubmissionOptions + data class PresentationSubmissionOptionsAnoncreds( val presentationDefinitionRequest: String ) : PresentationSubmissionOptions diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt index 487a4f8d3..16bc15c74 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt @@ -17,13 +17,19 @@ import kotlinx.serialization.json.longOrNull class DescriptorPath(private val jsonElement: JsonElement) { - fun getValue(path: String): Any? { - val regex = Regex("[\\[\\]`.]+") - val segments = path.split(regex).filter { it.isNotEmpty() }.drop(1) + fun getValue (path: String): Any? { + val regex = Regex("[/\\[\\]`.]+") + val segments = path.split(regex).filter { it.isNotEmpty() } + + // Drop the first segment if it starts with "$" + val filteredSegments = if (path.startsWith("$")) segments.drop(1) else segments + + +// val segments = path.split(regex).filter { it.isNotEmpty() }.drop(1) val jsonObject = jsonElement.jsonObject var current: Any? = jsonObject - segments.forEach { segment -> + filteredSegments.forEach { segment -> when (current) { is JsonObject -> { if ((current as JsonObject).contains(segment)) { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index fea97e766..ecf880217 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -22,6 +22,12 @@ import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.util.Base64URL import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT +import eu.europa.ec.eudi.sdjwt.JwtSignatureVerifier +import eu.europa.ec.eudi.sdjwt.KeyBindingVerifier +import eu.europa.ec.eudi.sdjwt.NoSignatureValidation +import eu.europa.ec.eudi.sdjwt.SdJwtVerifier +import eu.europa.ec.eudi.sdjwt.recreateClaimsAndDisclosuresPerClaim +import io.iohk.atala.prism.didcomm.didpeer.core.toJsonElement import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.setl.rdf.normalization.RdfNormalize @@ -76,6 +82,7 @@ import org.hyperledger.identus.walletsdk.domain.models.KeyValue import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.PresentationClaims import org.hyperledger.identus.walletsdk.domain.models.RequestedPredicates +import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.StorableCredential import org.hyperledger.identus.walletsdk.domain.models.UnknownError import org.hyperledger.identus.walletsdk.domain.models.httpClient @@ -95,6 +102,7 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsAnoncreds import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsSDJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl @@ -759,12 +767,11 @@ open class PolluxImpl( val format: PresentationDefinition.InputDescriptor.PresentationFormat val inputDescriptor: PresentationDefinition.InputDescriptor when (type) { - CredentialType.JWT, - CredentialType.SDJWT -> { + CredentialType.JWT -> { if (options.name == null || options.purpose == null) { throw Exception("") } - if (options is JWTPresentationOptions || options is SDJWTPresentationOptions) { + if (options is JWTPresentationOptions) { if (presentationClaims !is JWTPresentationClaims) { throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation claims must be ${JWTPresentationClaims::class.simpleName}") } @@ -773,7 +780,7 @@ open class PolluxImpl( val mutableListFields: MutableList = paths.map { path -> PresentationDefinition.InputDescriptor.Constraints.Field( - path = arrayOf("$.vc.credentialSubject.$path", "$.credentialSubject.$path"), + path = arrayOf("$.vc.credentialSubject.$path", "$.credentialSubject.$path", "$.$path"), id = UUID.randomUUID().toString(), optional = false, filter = presentationClaims.claims[path], @@ -803,66 +810,105 @@ open class PolluxImpl( PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED ) - if (type == CredentialType.JWT) { - options as JWTPresentationOptions - val jwt = options.jwt - if (jwt.isEmpty()) { - throw PolluxError.InvalidJWTPresentationDefinitionError("Presentation option must contain at least one valid JWT alg that is not empty.") + options as JWTPresentationOptions + val jwt = options.jwt + if (jwt.isEmpty()) { + throw PolluxError.InvalidJWTPresentationDefinitionError("Presentation option must contain at least one valid JWT alg that is not empty.") + } + val format = PresentationDefinition.InputDescriptor.PresentationFormat( + jwt = jwt.let { + PresentationDefinition.InputDescriptor.JwtFormat( + jwt.toList() + ) } - val format = PresentationDefinition.InputDescriptor.PresentationFormat( - jwt = jwt.let { - PresentationDefinition.InputDescriptor.JwtFormat( - jwt.toList() - ) - } - ) - inputDescriptor = PresentationDefinition.InputDescriptor( - name = options.name, - purpose = options.purpose, - constraints = constraints, - format = format + ) + inputDescriptor = PresentationDefinition.InputDescriptor( + name = options.name, + purpose = options.purpose, + constraints = constraints, + format = format + ) + return Json.encodeToString( + JWTPresentationDefinitionRequest( + presentationDefinition = PresentationDefinition( + inputDescriptors = arrayOf(inputDescriptor), + format = format + ), + options = JWTPresentationDefinitionRequest.PresentationDefinitionOptions( + domain = options.domain, + challenge = options.challenge + ) ) - return Json.encodeToString( - JWTPresentationDefinitionRequest( - presentationDefinition = PresentationDefinition( - inputDescriptors = arrayOf(inputDescriptor), - format = format + ) + } + } + + CredentialType.SDJWT -> { + if (options.name == null || options.purpose == null) { + throw Exception("") + } + if (options is SDJWTPresentationOptions) { + if (presentationClaims !is SDJWTPresentationClaims) { + throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation claims must be ${SDJWTPresentationClaims::class.simpleName}") + } + + val paths = presentationClaims.claims.keys + val mutableListFields: MutableList = + paths.map { path -> + PresentationDefinition.InputDescriptor.Constraints.Field( + path = arrayOf("$.vc.credentialSubject.$path", "$.credentialSubject.$path", "$.$path"), + id = UUID.randomUUID().toString(), + optional = false, + filter = presentationClaims.claims[path], + name = path, + ) + } as MutableList + + presentationClaims.issuer?.let { issuer -> + mutableListFields.add( + PresentationDefinition.InputDescriptor.Constraints.Field( + path = arrayOf("$.issuer", "$.iss", "$.vc.iss", "$.vc.issuer"), + optional = false, + id = UUID.randomUUID().toString(), + filter = InputFieldFilter( + type = "String", + pattern = issuer ), - options = JWTPresentationDefinitionRequest.PresentationDefinitionOptions( - domain = options.domain, - challenge = options.challenge - ) + name = "issuer" ) ) - } else { - options as SDJWTPresentationOptions - val sdjwt = options.sdjwt - val format = PresentationDefinition.InputDescriptor.PresentationFormat( - sdjwt = sdjwt.let { - PresentationDefinition.InputDescriptor.JwtFormat( - sdjwt.toList() - ) - } - ) - inputDescriptor = PresentationDefinition.InputDescriptor( - name = options.name, - purpose = options.purpose, - constraints = constraints, - format = format + } + val constraints = + PresentationDefinition.InputDescriptor.Constraints( + fields = mutableListFields.toTypedArray(), + limitDisclosure = + PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED ) - return Json.encodeToString( - SDJWTPresentationDefinitionRequest( - presentationDefinition = PresentationDefinition( - inputDescriptors = arrayOf(inputDescriptor), - format = format - ), - options = SDJWTPresentationDefinitionRequest.PresentationDefinitionOptions( - presentationFrame = options.presentationFrame - ) + options as SDJWTPresentationOptions + val sdjwt = options.sdjwt + val format = PresentationDefinition.InputDescriptor.PresentationFormat( + sdjwt = sdjwt.let { + PresentationDefinition.InputDescriptor.JwtFormat( + sdjwt.toList() + ) + } + ) + inputDescriptor = PresentationDefinition.InputDescriptor( + name = options.name, + purpose = options.purpose, + constraints = constraints, + format = format + + ) + return Json.encodeToString( + SDJWTPresentationDefinitionRequest( + presentationDefinition = PresentationDefinition( + inputDescriptors = arrayOf(inputDescriptor), + format = format ) ) - } + ) } } @@ -927,7 +973,7 @@ open class PolluxImpl( } throw PolluxError.CredentialTypeNotSupportedError( "Credential type ${type.type} not supported. " + - "Must be ${CredentialType.JWT.type}, ${CredentialType.SDJWT.type} or ${CredentialType.ANONCREDS_PROOF_REQUEST.type}" + "Must be ${CredentialType.JWT.type}, ${CredentialType.SDJWT.type} or ${CredentialType.ANONCREDS_PROOF_REQUEST.type}" ) } @@ -1074,225 +1120,397 @@ open class PolluxImpl( presentationSubmissionString: String, options: PresentationSubmissionOptions ): Boolean { - if (options::class == PresentationSubmissionOptionsJWT::class) { - val presentationSubmissionJsonObject = - Json.decodeFromString(presentationSubmissionString).jsonObject - val presentationSubmission = - presentationSubmissionJsonObject["presentation_submission"]?.let { presentationSubmissionField -> - val submission = - Json.decodeFromJsonElement( - presentationSubmissionField - ) - var arrayStrings: Array = arrayOf() - - if (submission.descriptorMap.isNotEmpty()) { - val firstDescriptorItem = submission.descriptorMap.first() - // Assume the path denotes a direct key in the JSON and strip out JSONPath or XPath specific characters if any. - val path = firstDescriptorItem.path.removePrefix("$.") - .removeSuffix("[0]") // Adjust based on actual path format - arrayStrings = - presentationSubmissionJsonObject[path]?.jsonArray?.map { it.jsonPrimitive.content } - ?.toTypedArray() - ?: arrayOf() - } - PresentationSubmission(submission, arrayStrings) - } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") + return when (options) { + is PresentationSubmissionOptionsJWT -> { + verifyJWTPresentation(presentationSubmissionString, options) + } - val presentationDefinitionRequestString = - (options as PresentationSubmissionOptionsJWT).presentationDefinitionRequest - val presentationDefinitionRequest = - Json.decodeFromString(presentationDefinitionRequestString) - val inputDescriptors = presentationDefinitionRequest.presentationDefinition.inputDescriptors - val descriptorMap = DescriptorPath(Json.encodeToJsonElement(presentationSubmission)) - val descriptorMaps = presentationSubmission.presentationSubmission.descriptorMap - descriptorMaps.forEach { descriptorItem -> - if (descriptorItem.format != DescriptorItemFormat.JWT_VP.value) { - throw PolluxError.VerificationUnsuccessful("Invalid submission, ${descriptorItem.path} expected to have format ${DescriptorItemFormat.JWT_VP.value}") + is PresentationSubmissionOptionsSDJWT -> { + verifySDJWTPresentation(presentationSubmissionString, options) + } + + is PresentationSubmissionOptionsAnoncreds -> { + verifyAnoncredsPresentation(presentationSubmissionString, options) + } + + else -> return false + } + } + + private suspend fun verifyJWTPresentation( + presentationSubmissionString: String, + options: PresentationSubmissionOptionsJWT + ): Boolean { + val presentationSubmissionJsonObject = + Json.decodeFromString(presentationSubmissionString).jsonObject + val presentationSubmission = + presentationSubmissionJsonObject["presentation_submission"]?.let { presentationSubmissionField -> + val submission = + Json.decodeFromJsonElement( + presentationSubmissionField + ) + var arrayStrings: Array = arrayOf() + + if (submission.descriptorMap.isNotEmpty()) { + val firstDescriptorItem = submission.descriptorMap.first() + // Assume the path denotes a direct key in the JSON and strip out JSONPath or XPath specific characters if any. + val path = firstDescriptorItem.path.removePrefix("$.") + .removeSuffix("[0]") // Adjust based on actual path format + arrayStrings = + presentationSubmissionJsonObject[path]?.jsonArray?.map { it.jsonPrimitive.content } + ?.toTypedArray() + ?: arrayOf() } + PresentationSubmission(submission, arrayStrings) + } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") + + val presentationDefinitionRequestString = + (options as PresentationSubmissionOptionsJWT).presentationDefinitionRequest + val presentationDefinitionRequest = + Json.decodeFromString(presentationDefinitionRequestString) + + val inputDescriptors = presentationDefinitionRequest.presentationDefinition.inputDescriptors + val descriptorMap = DescriptorPath(Json.encodeToJsonElement(presentationSubmission)) + val descriptorMaps = presentationSubmission.presentationSubmission.descriptorMap + descriptorMaps.forEach { descriptorItem -> + if (descriptorItem.format != DescriptorItemFormat.JWT_VP.value) { + throw PolluxError.VerificationUnsuccessful("Invalid submission, ${descriptorItem.path} expected to have format ${DescriptorItemFormat.JWT_VP.value}") + } - var newPath: String? = null - if (!descriptorItem.path.contains("verifiablePresentation")) { - newPath = - PresentationSubmission.Submission.DescriptorItem.replacePathWithVerifiablePresentation( - descriptorItem.path - ) + var newPath: String? = null + if (!descriptorItem.path.contains("verifiablePresentation")) { + newPath = + PresentationSubmission.Submission.DescriptorItem.replacePathWithVerifiablePresentation( + descriptorItem.path + ) + } + val jws = + descriptorMap.getValue(newPath ?: descriptorItem.path) + ?: throw PolluxError.VerificationUnsuccessful("Could not find ${descriptorItem.path} value") + val presentation = JWTCredential.fromJwtString(jws as String) + val issuer = presentation.issuer + + val presentationDefinitionOptions = presentationDefinitionRequest.options + val challenge = presentationDefinitionOptions.challenge + if (challenge.isNotBlank()) { + val nonce = presentation.nonce + if (nonce.isNullOrBlank()) { + throw PolluxError.VerificationUnsuccessful("Invalid submission, ${descriptorItem.path} does snot contain a nonce with a valid signature for the challenge.") } - val jws = - descriptorMap.getValue(newPath ?: descriptorItem.path) - ?: throw PolluxError.VerificationUnsuccessful("Could not find ${descriptorItem.path} value") - val presentation = JWTCredential.fromJwtString(jws as String) - val issuer = presentation.issuer - - val presentationDefinitionOptions = presentationDefinitionRequest.options - val challenge = presentationDefinitionOptions.challenge - if (challenge.isNotBlank()) { - val nonce = presentation.nonce - if (nonce.isNullOrBlank()) { - throw PolluxError.VerificationUnsuccessful("Invalid submission, ${descriptorItem.path} does snot contain a nonce with a valid signature for the challenge.") - } - if (challenge != nonce) { - throw PolluxError.VerificationUnsuccessful("Invalid submission, the signature from ${descriptorItem.path} is not valid for the challenge.") - } + if (challenge != nonce) { + throw PolluxError.VerificationUnsuccessful("Invalid submission, the signature from ${descriptorItem.path} is not valid for the challenge.") } + } - descriptorItem.pathNested?.let { pathNested -> - val verifiableCredentialMapper = - DescriptorPath(Json.encodeToJsonElement(presentation)) - val value = verifiableCredentialMapper.getValue(pathNested.path) - value?.let { vc -> - val verifiableCredential = JWTCredential.fromJwtString(vc as String) + descriptorItem.pathNested?.let { pathNested -> + val verifiableCredentialMapper = + DescriptorPath(Json.encodeToJsonElement(presentation)) + val value = verifiableCredentialMapper.getValue(pathNested.path) + value?.let { vc -> + val verifiableCredential = JWTCredential.fromJwtString(vc as String) -// val isRevoked = isCredentialRevoked(verifiableCredential) + val isRevoked = isCredentialRevoked(verifiableCredential) -// if (isRevoked) { -// throw PolluxError.VerificationUnsuccessful("Provided credential is revoked") -// } + if (isRevoked) { + throw PolluxError.VerificationUnsuccessful("Provided credential is revoked") + } - if (verifiableCredential.subject != issuer) { - throw PolluxError.VerificationUnsuccessful("Invalid submission,") - } + if (verifiableCredential.subject != issuer) { + throw PolluxError.VerificationUnsuccessful("Invalid submission,") + } - val didDocHolder = castor.resolveDID(verifiableCredential.issuer) - val authenticationMethodHolder = - didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } - ?: throw PolluxError.VerificationUnsuccessful("Holder core properties must contain Authentication") - val ecPublicKeysHolder = - extractEcPublicKeyFromVerificationMethod(authenticationMethodHolder) + val didDocHolder = castor.resolveDID(verifiableCredential.issuer) + val authenticationMethodHolder = + didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } + ?: throw PolluxError.VerificationUnsuccessful("Holder core properties must contain Authentication") + val ecPublicKeysHolder = + extractEcPublicKeyFromVerificationMethod(authenticationMethodHolder) - if (!verifyJWTSignatureWithEcPublicKey( - verifiableCredential.id, - ecPublicKeysHolder - ) - ) { - throw PolluxError.VerificationUnsuccessful("Invalid presentation credential JWT Signature") - } + if (!verifyJWTSignatureWithEcPublicKey( + verifiableCredential.id, + ecPublicKeysHolder + ) + ) { + throw PolluxError.VerificationUnsuccessful("Invalid presentation credential JWT Signature") + } - // Now we are going to validate the requested fields with the provided credentials - val verifiableCredentialDescriptorPath = - DescriptorPath(Json.encodeToJsonElement(verifiableCredential)) - val inputDescriptor = - inputDescriptors.find { it.id == descriptorItem.id } - if (inputDescriptor != null) { - val constraints = inputDescriptor.constraints - val fields = constraints.fields - if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { - fields?.forEach { field -> - val optional = field.optional - if (!optional) { - var validClaim = false - var reason = "" - val paths = field.path - paths.forEach { path -> - val fieldValue = - verifiableCredentialDescriptorPath.getValue(path) - if (fieldValue != null) { - if (field.filter != null) { - val filter: InputFieldFilter = field.filter - filter.pattern?.let { pattern -> - val regexPattern = Regex(pattern) - if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { + // Now we are going to validate the requested fields with the provided credentials + val verifiableCredentialDescriptorPath = + DescriptorPath(Json.encodeToJsonElement(verifiableCredential)) + val inputDescriptor = + inputDescriptors.find { it.id == descriptorItem.id } + if (inputDescriptor != null) { + val constraints = inputDescriptor.constraints + val fields = constraints.fields + if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { + fields?.forEach { field -> + val optional = field.optional + if (!optional) { + var validClaim = false + var reason = "" + val paths = field.path + paths.forEach { path -> + val fieldValue = + verifiableCredentialDescriptorPath.getValue(path) + if (fieldValue != null) { + if (field.filter != null) { + val filter: InputFieldFilter = field.filter + filter.pattern?.let { pattern -> + val regexPattern = Regex(pattern) + if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $pattern but got $fieldValue" + } + } + filter.enum?.let { enum -> + enum.forEach { predicate -> + if (fieldValue == predicate) { validClaim = true return@forEach - } else { - reason = - "Expected the $path field to be $pattern but got $fieldValue" } } - filter.enum?.let { enum -> - enum.forEach { predicate -> - if (fieldValue == predicate) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" - } - } - filter.const?.let { const -> - const.forEach { constValue -> - if (fieldValue == constValue) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" - } + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" } - filter.value?.let { value -> - if (value == fieldValue) { + } + filter.const?.let { const -> + const.forEach { constValue -> + if (fieldValue == constValue) { validClaim = true return@forEach - } else { - reason = - "Expected the $path field to be $value but got $fieldValue" } } - } else { - reason = - "Input field filter for ${field.name} is null" + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" + } + } + filter.value?.let { value -> + if (value == fieldValue) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $value but got $fieldValue" + } } } else { - reason = "Field value for path $path is null" + reason = + "Input field filter for ${field.name} is null" } + } else { + reason = "Field value for path $path is null" } - if (!validClaim) { - throw PolluxError.VerificationUnsuccessful(reason) - } + } + if (!validClaim) { + throw PolluxError.VerificationUnsuccessful(reason) } } } } } - ?: throw PolluxError.VerificationUnsuccessful("Invalid submission, no value found for $pathNested") - return true + } ?: throw PolluxError.VerificationUnsuccessful("Invalid submission, no value found for $pathNested") + return true + } + } + return false + } + + private suspend fun verifySDJWTPresentation( + presentationSubmissionString: String, + options: PresentationSubmissionOptionsSDJWT + ): Boolean { + val presentationSubmissionJsonObject = + Json.decodeFromString(presentationSubmissionString).jsonObject + val presentationSubmission = + presentationSubmissionJsonObject["presentation_submission"]?.let { presentationSubmissionField -> + val submission = + Json.decodeFromJsonElement( + presentationSubmissionField + ) + var arrayStrings: Array = arrayOf() + + if (submission.descriptorMap.isNotEmpty()) { + val firstDescriptorItem = submission.descriptorMap.first() + // Assume the path denotes a direct key in the JSON and strip out JSONPath or XPath specific characters if any. + val path = firstDescriptorItem.path.removePrefix("$.") + .removeSuffix("[0]") // Adjust based on actual path format + arrayStrings = + presentationSubmissionJsonObject[path]?.jsonArray?.map { it.jsonPrimitive.content } + ?.toTypedArray() + ?: arrayOf() } + PresentationSubmission(submission, arrayStrings) + } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") + + val presentationDefinitionRequestString = + options.presentationDefinitionRequest + val presentationDefinitionRequest = + Json.decodeFromString(presentationDefinitionRequestString) + + val descriptorMap = DescriptorPath(Json.encodeToJsonElement(presentationSubmission)) + + val descriptorMaps = presentationSubmission.presentationSubmission.descriptorMap + descriptorMaps.forEach { descriptorItem -> + if (descriptorItem.format != DescriptorItemFormat.SD_JWT_VP.value) { + throw PolluxError.VerificationUnsuccessful("Invalid submission, ${descriptorItem.path} expected to have format ${DescriptorItemFormat.JWT_VP.value}") } - } else if (options::class == PresentationSubmissionOptionsAnoncreds::class) { - val presentationDefinitionRequest = - (options as PresentationSubmissionOptionsAnoncreds).presentationDefinitionRequest - val anoncredsPresentationDefinitionRequest = - Json.decodeFromString(presentationDefinitionRequest) - - val presentationRequest = PresentationRequest(Json.encodeToString(anoncredsPresentationDefinitionRequest)) - val presentation = Presentation(presentationSubmissionString) - val presentationJsonObject = Json.parseToJsonElement(presentationSubmissionString).jsonObject - if (!presentationJsonObject.containsKey("identifiers")) { - throw Exception() + + var newPath: String? = null + if (!descriptorItem.path.contains("verifiablePresentation")) { + newPath = + PresentationSubmission.Submission.DescriptorItem.replacePathWithVerifiablePresentation( + descriptorItem.path + ) } - val identifiers = presentationJsonObject["identifiers"]!!.jsonArray - val identifier = identifiers.first().jsonObject - if (!identifier.containsKey("schema_id")) { - throw Exception() + val jws = + descriptorMap.getValue(newPath ?: descriptorItem.path) + ?: throw PolluxError.VerificationUnsuccessful("Could not find ${descriptorItem.path} value") + + val verification = SdJwtVerifier.verifyPresentation( + JwtSignatureVerifier.NoSignatureValidation, + KeyBindingVerifier.MustNotBePresent, + jws as String + ) + if (verification.isFailure) { + throw PolluxError.VerificationUnsuccessful("Invalid SD-JWT presentation") } - val schemaId = identifier["schema_id"]!!.jsonPrimitive.content - val schema = getSchema(schemaId) - if (!identifier.containsKey("cred_def_id")) { - throw Exception() + val presentation = SDJWTCredential.fromSDJwtString(jws) + + val all = presentation.sdjwt.recreateClaimsAndDisclosuresPerClaim { it.second } + val claimsAndDisclosures = DescriptorPath(all.first.toJsonElement()) + + val inputDescriptors = presentationDefinitionRequest.presentationDefinition.inputDescriptors + + val inputDescriptor = + inputDescriptors.find { it.id == descriptorItem.id } + if (inputDescriptor != null) { + val constraints = inputDescriptor.constraints + val fields = constraints.fields + if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { + fields?.forEach { field -> + val optional = field.optional + if (!optional) { + var validClaim = false + var reason = "" + val paths = field.path + paths.forEach { path -> + val fieldValue = + claimsAndDisclosures.getValue(path) + if (fieldValue != null) { + if (field.filter != null) { + val filter: InputFieldFilter = field.filter + filter.pattern?.let { pattern -> + val regexPattern = Regex(pattern) + if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $pattern but got $fieldValue" + } + } + filter.enum?.let { enum -> + enum.forEach { predicate -> + if (fieldValue == predicate) { + validClaim = true + return@forEach + } + } + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" + } + } + filter.const?.let { const -> + const.forEach { constValue -> + if (fieldValue == constValue) { + validClaim = true + return@forEach + } + } + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" + } + } + filter.value?.let { value -> + if (value == fieldValue) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $value but got $fieldValue" + } + } + } else { + reason = + "Input field filter for ${field.name} is null" + } + } else { + reason = "Field value for path $path is null" + } + } + if (!validClaim) { + throw PolluxError.VerificationUnsuccessful(reason) + } + } + } + } } - val credDefId = identifier["cred_def_id"]!!.jsonPrimitive.content - val credentialDefinition = getCredentialDefinition(credDefId) - - val schemaMap: Map = mapOf(Pair(schemaId, schema)) - val credDefinition: Map = mapOf(Pair(credDefId, credentialDefinition)) - - return Verifier().verifyPresentation( - presentation = presentation, - presReq = presentationRequest, - schemas = schemaMap, - credDefs = credDefinition, - revRegDefs = null, - revStatusLists = null, - nonrevokeIntervalOverride = null - ) + return true } return false } + private suspend fun verifyAnoncredsPresentation( + presentationSubmissionString: String, + options: PresentationSubmissionOptionsAnoncreds + ): Boolean { + val presentationDefinitionRequest = + (options as PresentationSubmissionOptionsAnoncreds).presentationDefinitionRequest + val anoncredsPresentationDefinitionRequest = + Json.decodeFromString(presentationDefinitionRequest) + + val presentationRequest = PresentationRequest(Json.encodeToString(anoncredsPresentationDefinitionRequest)) + val presentation = Presentation(presentationSubmissionString) + val presentationJsonObject = Json.parseToJsonElement(presentationSubmissionString).jsonObject + if (!presentationJsonObject.containsKey("identifiers")) { + throw Exception() + } + val identifiers = presentationJsonObject["identifiers"]!!.jsonArray + val identifier = identifiers.first().jsonObject + if (!identifier.containsKey("schema_id")) { + throw Exception() + } + val schemaId = identifier["schema_id"]!!.jsonPrimitive.content + val schema = getSchema(schemaId) + + if (!identifier.containsKey("cred_def_id")) { + throw Exception() + } + val credDefId = identifier["cred_def_id"]!!.jsonPrimitive.content + val credentialDefinition = getCredentialDefinition(credDefId) + + val schemaMap: Map = mapOf(Pair(schemaId, schema)) + val credDefinition: Map = mapOf(Pair(credDefId, credentialDefinition)) + + return Verifier().verifyPresentation( + presentation = presentation, + presReq = presentationRequest, + schemas = schemaMap, + credDefs = credDefinition, + revRegDefs = null, + revStatusLists = null, + nonrevokeIntervalOverride = null + ) + } + internal fun verifyJWTSignatureWithEcPublicKey( jwtString: String, ecPublicKeys: Array diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt index 74a8957f9..322c3db9e 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationDefinitionRequest.kt @@ -45,15 +45,8 @@ data class JWTPresentationDefinitionRequest( @Serializable data class SDJWTPresentationDefinitionRequest( @SerialName("presentation_definition") - val presentationDefinition: PresentationDefinition, - val options: PresentationDefinitionOptions? = null -) : PresentationDefinitionRequest { - - @Serializable - data class PresentationDefinitionOptions( - val presentationFrame: Map - ) -} + val presentationDefinition: PresentationDefinition +) : PresentationDefinitionRequest @Serializable data class PresentationDefinition( diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationSubmission.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationSubmission.kt index 106f473e1..9958b38d0 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationSubmission.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/PresentationSubmission.kt @@ -6,7 +6,8 @@ import java.util.UUID enum class DescriptorItemFormat(val value: String) { JWT_VC("jwt_vc"), - JWT_VP("jwt_vp") + JWT_VP("jwt_vp"), + SD_JWT_VP("sdjwt") } @Serializable diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index 365ea4449..91c931329 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -1,32 +1,54 @@ package org.hyperledger.identus.walletsdk.pollux.models + +import com.nimbusds.jose.JOSEObjectType +import com.nimbusds.jose.JWSAlgorithm +import com.nimbusds.jose.crypto.ECDSASigner +import com.nimbusds.jwt.SignedJWT +import eu.europa.ec.eudi.sdjwt.ClaimValidations.primitiveClaim import eu.europa.ec.eudi.sdjwt.JsonPointer import eu.europa.ec.eudi.sdjwt.JwtAndClaims import eu.europa.ec.eudi.sdjwt.JwtSignatureVerifier import eu.europa.ec.eudi.sdjwt.NoSignatureValidation import eu.europa.ec.eudi.sdjwt.SdJwt +import eu.europa.ec.eudi.sdjwt.SdJwtFactory +import eu.europa.ec.eudi.sdjwt.SdJwtIssuer import eu.europa.ec.eudi.sdjwt.SdJwtVerifier +import eu.europa.ec.eudi.sdjwt.exp +import eu.europa.ec.eudi.sdjwt.iat +import eu.europa.ec.eudi.sdjwt.iss +import eu.europa.ec.eudi.sdjwt.name +import eu.europa.ec.eudi.sdjwt.nimbus +import eu.europa.ec.eudi.sdjwt.plain import eu.europa.ec.eudi.sdjwt.present +import eu.europa.ec.eudi.sdjwt.recreateClaims +import eu.europa.ec.eudi.sdjwt.sd +import eu.europa.ec.eudi.sdjwt.sdJwt import eu.europa.ec.eudi.sdjwt.serialize +import eu.europa.ec.eudi.sdjwt.sub import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Transient import kotlinx.serialization.builtins.ArraySerializer import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonTransformingSerializer import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonPrimitive -import org.hyperledger.identus.walletsdk.domain.models.ApolloError import org.hyperledger.identus.walletsdk.domain.models.Claim import org.hyperledger.identus.walletsdk.domain.models.ClaimType import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.domain.models.CredentialOperationsOptions +import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential import org.hyperledger.identus.walletsdk.domain.models.StorableCredential -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.ExportableKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey +import java.security.interfaces.ECPrivateKey +import java.util.* +import kotlinx.serialization.json.put @OptIn(ExperimentalSerializationApi::class) data class SDJWTCredential( @@ -37,7 +59,8 @@ data class SDJWTCredential( get() = sdjwtString @Transient - override val issuer: String = sdjwt.jwt.second["iss"]?.jsonPrimitive?.content ?: throw Exception("Most contain issuer") // TODO: Custom exception + override val issuer: String = sdjwt.jwt.second["iss"]?.jsonPrimitive?.content + ?: throw Exception("Most contain issuer") // TODO: Custom exception override val subject: String? get() = sdjwt.jwt.second["sub"]?.jsonPrimitive?.content @@ -64,7 +87,25 @@ data class SDJWTCredential( override var revoked: Boolean? = null - override suspend fun presentation(attachmentFormat: String, request: ByteArray, options: List): String { + override suspend fun presentation( + attachmentFormat: String, + request: ByteArray, + options: List + ): String { + val jwtPresentationDefinitionRequest = + Json.decodeFromString(String(request, Charsets.UTF_8)) + val descriptorItems = + jwtPresentationDefinitionRequest.presentationDefinition.inputDescriptors.map { inputDescriptor -> + if (inputDescriptor.format != null && (inputDescriptor.format.sdjwt == null || inputDescriptor.format.sdjwt.alg.isEmpty())) { + throw PolluxError.InvalidCredentialDefinitionError() + } + PresentationSubmission.Submission.DescriptorItem( + id = inputDescriptor.id, + format = DescriptorItemFormat.SD_JWT_VP.value, + path = "$.verifiablePresentation[0]" + ) + }.toTypedArray() + var disclosingClaims: List? = null for (option in options) { @@ -77,8 +118,20 @@ data class SDJWTCredential( val included = disclosingClaims ?.mapNotNull { JsonPointer.parse(it) } ?.toSet() + val presentation = sdjwt.present(included!!) - return presentation!!.serialize { (jwt, _) -> jwt } + val sdjwtString = presentation!!.serialize { (jwt, _) -> jwt } + + return Json.encodeToString( + PresentationSubmission( + presentationSubmission = PresentationSubmission.Submission( + definitionId = jwtPresentationDefinitionRequest.presentationDefinition.id + ?: UUID.randomUUID().toString(), + descriptorMap = descriptorItems + ), + verifiablePresentation = arrayOf(sdjwtString) + ) + ) } /** @@ -158,7 +211,8 @@ data class SDJWTCredential( fun fromSDJwtString(sdjwtString: String): SDJWTCredential { var credential: SDJWTCredential runBlocking { - val sdjwt = SdJwtVerifier.verifyIssuance(JwtSignatureVerifier.NoSignatureValidation, sdjwtString).getOrThrow() + val sdjwt = + SdJwtVerifier.verifyIssuance(JwtSignatureVerifier.NoSignatureValidation, sdjwtString).getOrThrow() credential = SDJWTCredential(sdjwtString, sdjwt) } return credential diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index d4ce489bd..1fa0dba2d 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -74,6 +74,7 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.issueCredential.Off import org.hyperledger.identus.walletsdk.edgeagent.protocols.outOfBand.OutOfBandInvitation import org.hyperledger.identus.walletsdk.edgeagent.protocols.outOfBand.PrismOnboardingInvitation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.RequestPresentation +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPreparePresentationOptions import org.hyperledger.identus.walletsdk.logger.LoggerMock import org.hyperledger.identus.walletsdk.mercury.ApiMock import org.hyperledger.identus.walletsdk.pluto.CredentialRecovery @@ -88,6 +89,7 @@ import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -1999,32 +2001,19 @@ class EdgeAgentTests { toDID = toDid, presentationClaims = SDJWTPresentationClaims( claims = mapOf( - "familyName" to InputFieldFilter( + "first_name" to InputFieldFilter( type = "string", pattern = "Wonderland" ), - "givenName" to InputFieldFilter( + "last_name" to InputFieldFilter( type = "string", pattern = "Alice" ), - "drivingClass" to InputFieldFilter( - type = "integer", - pattern = "3" - ), - "dateOfIssuance" to InputFieldFilter( - type = "string", - pattern = "2020-11-13T20:20:39+00:00" - ), "emailAddress" to InputFieldFilter( type = "string", pattern = "alice@wonderland.com" - ), - "drivingLicenseID" to InputFieldFilter( - type = "string", - pattern = "12345" ) - ), - presentationFrame = mapOf("email" to true) + ) ), domain = "domain", challenge = "challenge" @@ -2038,12 +2027,12 @@ class EdgeAgentTests { assertEquals(1, sentMessage.attachments.size) assertTrue(sentMessage.attachments.first().data::class == AttachmentBase64::class) val json = sentMessage.attachments.first().data.getDataAsJsonString() - val presentationDefinition = Json.decodeFromString(json) + val presentationDefinition = Json.decodeFromString(json) assertNotNull(presentationDefinition.presentationDefinition.format.sdjwt) } @Test - fun `test handlePresentationDefinitionRequest`() = runTest { + fun `test handlePresentationDefinitionRequest for SD-JWT with no options, fails`() = runTest { val agent = spy( EdgeAgent( apollo = apolloMock, @@ -2087,10 +2076,75 @@ class EdgeAgentTests { """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjI0YTdlNWU4LWQ3YjQtNDUxYy1hOThkLTA3ZDY4NjVhMzQwYSIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjNmMWYzYTliLThjZDUtNDZkMS04Y2E0LTBlYzYyN2YxZTdmMiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiXSwiaWQiOiI5MjM3ZmNhMy1lZjcyLTQyOGEtYjIyYy02YzVmZTZmZTU0NWIiLCJuYW1lIjoiZmFtaWx5TmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZ2l2ZW5OYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5naXZlbk5hbWUiXSwiaWQiOiI3MmQ4NmYwMS04NGYzLTRhNDYtOGFiOC1hN2I0OGE5YjU2MTAiLCJuYW1lIjoiZ2l2ZW5OYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiQWxpY2UifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZHJpdmluZ0NsYXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nQ2xhc3MiXSwiaWQiOiIyNmI3ZmY3Zi1kOTcyLTQxZGYtYTNkZC0zYmE3YzhiNDAwOWEiLCJuYW1lIjoiZHJpdmluZ0NsYXNzIiwiZmlsdGVyIjp7InR5cGUiOiJpbnRlZ2VyIiwicGF0dGVybiI6IjMifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZGF0ZU9mSXNzdWFuY2UiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmRhdGVPZklzc3VhbmNlIl0sImlkIjoiYWZiZmI1NWMtMWY5Ni00ODlkLWJmOGUtYzZhNTUxYjg3ODFjIiwibmFtZSI6ImRhdGVPZklzc3VhbmNlIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiMjAyMC0xMS0xM1QyMDoyMDozOSswMDowMCJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyJdLCJpZCI6IjAyZGIxOTA0LWY3NTMtNDI2NC04ZTA0LWI5NGMxNWFkYzA3MyIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJhbGljZUB3b25kZXJsYW5kLmNvbSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIl0sImlkIjoiODgzYThkMzYtNWYzMy00OGVjLWJhYjktNzE0MGRiYWMyYTVmIiwibmFtZSI6ImRyaXZpbmdMaWNlbnNlSUQiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiIxMjM0NSJ9fV0sImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCJ9fV0sImZvcm1hdCI6eyJzZEp3dCI6eyJhbGciOlsiRVMyNTZrIl19fX0sIm9wdGlvbnMiOnsicHJlc2VudGF0aW9uRnJhbWUiOnt9fX0"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" ) - agent.preparePresentationForRequestProof( + assertFailsWith(EdgeAgentError.MissingOrNullFieldError::class) { + agent.preparePresentationForRequestProof( + request = RequestPresentation.fromMessage(msg), + credential = credential + ) + } + } + + @Test + fun `test handlePresentationDefinitionRequest for SD-JWT`() = runTest { + val agent = spy( + EdgeAgent( + apollo = apolloMock, + castor = castorMock, + pluto = plutoMock, + mercury = mercuryMock, + pollux = PolluxImpl(apolloMock, castorMock), + connectionManager = connectionManagerMock, + seed = seed, + api = null, + logger = LoggerMock() + ) + ) + + val privateKey = Secp256k1KeyPair.generateKeyPair( + seed = seed, + curve = KeyCurve(Curve.SECP256K1) + ).privateKey + val storablePrivateKeys = listOf( + StorablePrivateKey( + id = UUID.randomUUID().toString(), + restorationIdentifier = "secp256k1+priv", + data = privateKey.raw.base64UrlEncoded, + keyPathIndex = 0 + ) + ) + // Mock getDIDPrivateKeysByDID response + `when`(plutoMock.getDIDPrivateKeysByDID(any())).thenReturn(flow { emit(storablePrivateKeys) }) + `when`( + apolloMock.restorePrivateKey( + storablePrivateKeys.first().restorationIdentifier, + storablePrivateKeys.first().data + ) + ).thenReturn(privateKey) + + `when`(plutoMock.getPrismLastKeyPathIndex()).thenReturn(flow { emit(0) }) + + val credential = SDJWTCredential.fromSDJwtString( + "eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJkaWQ6cHJpc206MGE0YjU1MjE2OWUzMTU4NzgxNzQxZmJiZWZmZTgxMjEyNzg0ZDMyZDkwY2Y4ZjI2MjI5MjNmMTFmNmVjZDk2NjpDb1VCQ29JQkVqc0tCMjFoYzNSbGNqQVFBVW91Q2dselpXTndNalUyYXpFU0lRTGd6aHN1T3FoQXlJbXktYzhvOVptSUo0aVlfR2M4dHZOSVQzbDF3NThmMkJKRENnOWhkWFJvWlc1MGFXTmhkR2x2YmpBUUJFb3VDZ2x6WldOd01qVTJhekVTSVFMZ3poc3VPcWhBeUlteS1jOG85Wm1JSjRpWV9HYzh0dk5JVDNsMXc1OGYyQSIsIl9zZCI6WyJXVTFWMTZWbTdnd3R3SHRrRGVsdGF1VjlUNTl2SW4yZW9UQVdnU3dtdnQ0Il0sIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiZGlkOnByaXNtOmNlMzQwM2I1YTczMzg4MzAzNWQ2ZWM0M2JhMDc1YTQxYzljYzBhMzI1Nzk3N2Q4MGM3NWQ2MzE5YWRlMGVkNzAiLCJleHAiOjE3MzU2ODk2NjEsImlhdCI6MTUxNjIzOTAyMn0.ITRzKaRaY5GB5zIlgP1KdLYtthnededjXZFc9MJTjKCIcSgdyEBNJxPXHftAoC9I9db3GZ6r_Psmq7S_0f9NAA~WyI1M1R1MlN2bmM3OThaUnhHaEE2b0VRIiwiZW1haWxBZGRyZXNzIiwidGVzdEBpb2hrLmlvIl0~" + ) + + val msg = Json.decodeFromString( + """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjI0YTdlNWU4LWQ3YjQtNDUxYy1hOThkLTA3ZDY4NjVhMzQwYSIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjNmMWYzYTliLThjZDUtNDZkMS04Y2E0LTBlYzYyN2YxZTdmMiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiXSwiaWQiOiI5MjM3ZmNhMy1lZjcyLTQyOGEtYjIyYy02YzVmZTZmZTU0NWIiLCJuYW1lIjoiZmFtaWx5TmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZ2l2ZW5OYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5naXZlbk5hbWUiXSwiaWQiOiI3MmQ4NmYwMS04NGYzLTRhNDYtOGFiOC1hN2I0OGE5YjU2MTAiLCJuYW1lIjoiZ2l2ZW5OYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiQWxpY2UifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZHJpdmluZ0NsYXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nQ2xhc3MiXSwiaWQiOiIyNmI3ZmY3Zi1kOTcyLTQxZGYtYTNkZC0zYmE3YzhiNDAwOWEiLCJuYW1lIjoiZHJpdmluZ0NsYXNzIiwiZmlsdGVyIjp7InR5cGUiOiJpbnRlZ2VyIiwicGF0dGVybiI6IjMifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZGF0ZU9mSXNzdWFuY2UiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmRhdGVPZklzc3VhbmNlIl0sImlkIjoiYWZiZmI1NWMtMWY5Ni00ODlkLWJmOGUtYzZhNTUxYjg3ODFjIiwibmFtZSI6ImRhdGVPZklzc3VhbmNlIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiMjAyMC0xMS0xM1QyMDoyMDozOSswMDowMCJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyJdLCJpZCI6IjAyZGIxOTA0LWY3NTMtNDI2NC04ZTA0LWI5NGMxNWFkYzA3MyIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJhbGljZUB3b25kZXJsYW5kLmNvbSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIl0sImlkIjoiODgzYThkMzYtNWYzMy00OGVjLWJhYjktNzE0MGRiYWMyYTVmIiwibmFtZSI6ImRyaXZpbmdMaWNlbnNlSUQiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiIxMjM0NSJ9fV0sImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCJ9fV0sImZvcm1hdCI6eyJzZEp3dCI6eyJhbGciOlsiRVMyNTZrIl19fX0sIm9wdGlvbnMiOnsicHJlc2VudGF0aW9uRnJhbWUiOnt9fX0"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" + ) + + val presentation = agent.preparePresentationForRequestProof( request = RequestPresentation.fromMessage(msg), - credential = credential + credential = credential, + preparePresentationOptions = SDJWTPreparePresentationOptions( + presentationFrame = mapOf("/emailAddress" to true) + ) ) + + assertEquals(1, presentation.attachments.size) + val attachmentDescriptor = presentation.attachments.first() + val attachmentData = attachmentDescriptor.data + assertTrue(attachmentData is AttachmentBase64) + val jwt = attachmentData.getDataAsJsonString() + assertTrue(jwt.contains(".")) } val getCredentialDefinitionResponse = diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt new file mode 100644 index 000000000..1c4500d20 --- /dev/null +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt @@ -0,0 +1,37 @@ +package org.hyperledger.identus.walletsdk.pollux + +import junit.framework.TestCase.assertTrue +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.encodeToJsonElement +import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class DescriptorPathTest { + + @Test + fun testGetValue_whenDollarSign_thenValueFound() { + val submission = """{"presentation_submission":{"id":"3afa67f4-5181-4cec-a296-0f52705126a9","definition_id":"6ceedd50-187d-4731-b779-c7e1bf771191","descriptor_map":[{"id":"a319a6b7-b8b0-441f-a1ca-ed454d151ee5","format":"jwt_vp","path":"${'$'}.verifiablePresentation[0]","path_nested":{"id":"a319a6b7-b8b0-441f-a1ca-ed454d151ee5","format":"jwt_vc","path":"${'$'}.vp.verifiableCredential[0]"}}]},"verifiablePresentation":["eyJraWQiOiJkaWQ6cHJpc206Y2JjNmQyOGE1NTI0NWZhZGYwYmU4ZmU5NDZhY2Q2ZjRiZjJkN2E4MWVlMTYzZjVlM2U0M2YwOTM2NWM2NWIwZjpDb1VCQ29JQkVqc0tCMjFoYzNSbGNqQVFBVW91Q2dselpXTndNalUyYXpFU0lRUHVRTG4zaC1IMDlMN0lQYkhZSy0xNTZKdC1ScWlydUdWdlgwTzJ0VVRvNEJKRENnOWhkWFJvWlc1MGFXTmhkR2x2YmpBUUJFb3VDZ2x6WldOd01qVTJhekVTSVFQdVFMbjNoLUgwOUw3SVBiSFlLLTE1Nkp0LVJxaXJ1R1Z2WDBPMnRVVG80QSNhdXRoZW50aWNhdGlvbjAiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206Y2JjNmQyOGE1NTI0NWZhZGYwYmU4ZmU5NDZhY2Q2ZjRiZjJkN2E4MWVlMTYzZjVlM2U0M2YwOTM2NWM2NWIwZjpDb1VCQ29JQkVqc0tCMjFoYzNSbGNqQVFBVW91Q2dselpXTndNalUyYXpFU0lRUHVRTG4zaC1IMDlMN0lQYkhZSy0xNTZKdC1ScWlydUdWdlgwTzJ0VVRvNEJKRENnOWhkWFJvWlc1MGFXTmhkR2x2YmpBUUJFb3VDZ2x6WldOd01qVTJhekVTSVFQdVFMbjNoLUgwOUw3SVBiSFlLLTE1Nkp0LVJxaXJ1R1Z2WDBPMnRVVG80QSIsImF1ZCI6ImRvbWFpbiIsInZwIjp7IkBjb250ZXh0IjpbImh0dHBzOlwvXC93d3cudzMub3JnXC8yMDE4XC9jcmVkZW50aWFsc1wvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl0sInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SmhiR2NpT2lKRlV6STFOa3NpZlEuZXlKaGRXUWlPaUpoTVdVME1UVTFNQzAxWkRVMExUUmhOelF0T1RBeVppMWtOV1psTWpZeVpHRXhPVEFpTENKemRXSWlPaUprYVdRNmNISnBjMjA2WTJKak5tUXlPR0UxTlRJME5XWmhaR1l3WW1VNFptVTVORFpoWTJRMlpqUmlaakprTjJFNE1XVmxNVFl6WmpWbE0yVTBNMll3T1RNMk5XTTJOV0l3WmpwRGIxVkNRMjlKUWtWcWMwdENNakZvWXpOU2JHTnFRVkZCVlc5MVEyZHNlbHBYVG5kTmFsVXlZWHBGVTBsUlVIVlJURzR6YUMxSU1EbE1OMGxRWWtoWlN5MHhOVFpLZEMxU2NXbHlkVWRXZGxnd1R6SjBWVlJ2TkVKS1JFTm5PV2hrV0ZKdldsYzFNR0ZYVG1oa1IyeDJZbXBCVVVKRmIzVkRaMng2V2xkT2QwMXFWVEpoZWtWVFNWRlFkVkZNYmpOb0xVZ3dPVXczU1ZCaVNGbExMVEUxTmtwMExWSnhhWEoxUjFaMldEQlBNblJWVkc4MFFTSXNJbTVpWmlJNk1UY3lOekl3TXpVNE15d2lhWE56SWpvaVpHbGtPbkJ5YVhOdE9tTXdNamMwWkdObFltSTFNR0l5TlRFM05XTmxPVFEzTldNM05EZGhaamxpWW1WaVlUZGhabUptTWpFMU9UUTJNRFkzTVRjd1lXVTFaamxpWWpoaU1EazZRMjlWUWtOdlNVSkZhbk5MUWpJeGFHTXpVbXhqYWtGUlFWVnZkVU5uYkhwYVYwNTNUV3BWTW1GNlJWTkpVVXR5WjA1eVdXTTNWVmt5TVRFeGJsbHRObnBEV201bGRqZDVhRFZPY0ZFd09YQnpVM2c0ZEVjMGRYbFNTa1JEWnpsb1pGaFNiMXBYTlRCaFYwNW9aRWRzZG1KcVFWRkNSVzkxUTJkc2VscFhUbmROYWxVeVlYcEZVMGxSUzNKblRuSlpZemRWV1RJeE1URnVXVzAyZWtOYWJtVjJOM2xvTlU1d1VUQTVjSE5UZURoMFJ6UjFlVkVpTENKbGVIQWlPakUzTWprM09UVTFPRE1zSW5aaklqcDdJbU55WldSbGJuUnBZV3hUWTJobGJXRWlPbTUxYkd3c0luUmxjbTF6VDJaVmMyVWlPbTUxYkd3c0ltVjJhV1JsYm1ObElqcHVkV3hzTENKamNtVmtaVzUwYVdGc1UzVmlhbVZqZENJNmV5SmpiM1Z5YzJVaU9pSkpaR1Z1ZEhWeklGUnlZV2x1YVc1bklHTnZkWEp6WlNCRFpYSjBhV1pwWTJGMGFXOXVJREl3TWpRaWZTd2lZMjl1ZEdWNGRDSTZXeUpvZEhSd2N6cGNMMXd2ZDNkM0xuY3pMbTl5WjF3dk1qQXhPRnd2WTNKbFpHVnVkR2xoYkhOY0wzWXhJbDBzSW5KbFpuSmxjMmhUWlhKMmFXTmxJanB1ZFd4c0xDSjBlWEJsSWpwYklsWmxjbWxtYVdGaWJHVkRjbVZrWlc1MGFXRnNJbDBzSW1OeVpXUmxiblJwWVd4VGRHRjBkWE1pT25zaWMzUmhkSFZ6VUhWeWNHOXpaU0k2SWxKRlZrOURRVlJKVDA0aUxDSnpkR0YwZFhOTWFYTjBTVzVrWlhnaU9qSTFMQ0pwWkNJNkltaDBkSEE2WEM5Y0x6RXdMamt4TGpFd01DNHhNalk2T0RBd01Gd3ZjSEpwYzIwdFlXZGxiblJjTDJOeVpXUmxiblJwWVd3dGMzUmhkSFZ6WEM4MU1UUmxPRFV5T0MwMFlqTTRMVFEzTjJFdFlqQmxOQzB6TWpSaVltVXlNakEwTmpRak1qVWlMQ0owZVhCbElqb2lVM1JoZEhWelRHbHpkREl3TWpGRmJuUnllU0lzSW5OMFlYUjFjMHhwYzNSRGNtVmtaVzUwYVdGc0lqb2lhSFIwY0RwY0wxd3ZNVEF1T1RFdU1UQXdMakV5TmpvNE1EQXdYQzl3Y21semJTMWhaMlZ1ZEZ3dlkzSmxaR1Z1ZEdsaGJDMXpkR0YwZFhOY0x6VXhOR1U0TlRJNExUUmlNemd0TkRjM1lTMWlNR1UwTFRNeU5HSmlaVEl5TURRMk5DSjlmWDAuUFJnNkx0ZFV5TGNsQzlYbUVJLUxJWDQ4UWlzdVNfMDRCbDF3dHVXejNTamFHdXlSd0dRQ1ZxTldQUWFaZktNa2R2VUZ3bWtyMjZ4bE9fNDRlT2ZPeEEiXX0sIm5vbmNlIjoiMmYxODdjY2UtZDY1ZC00MGYzLTg2YzktYTk5ZTg3NzQ0ZGE3In0._UbbNfI9CUuUNgrLbECTw_20V-BSN-A4WmSuvj2lRbDiIkwCm5MJbbQrj8OiD85cJUEamW4QtQUr9-ckpE1aYg"]}""" + val descriptorPath = + DescriptorPath(Json.encodeToJsonElement(Json.decodeFromString(submission))) + val path = "\$.verifiablePresentation[0]" + val holderJws = descriptorPath.getValue(path) + assertNotNull(holderJws) + assertTrue(holderJws is String) + val path1 = "\$.verifiablePresentation" + val holderJws1 = descriptorPath.getValue(path1) + assertNotNull(holderJws1) + } + + @Test + fun testGetValue_whenNotDollarSign_thenValueFound() { + val claims = """{"sub":"did:prism:0a4b552169e3158781741fbbeffe81212784d32d90cf8f2622923f11f6ecd966:CoUBCoIBEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQLgzhsuOqhAyImy-c8o9ZmIJ4iY_Gc8tvNIT3l1w58f2BJDCg9hdXRoZW50aWNhdGlvbjAQBEouCglzZWNwMjU2azESIQLgzhsuOqhAyImy-c8o9ZmIJ4iY_Gc8tvNIT3l1w58f2A","iss":"did:prism:ce3403b5a733883035d6ec43ba075a41c9cc0a3257977d80c75d6319ade0ed70","exp":1735689661,"iat":1516239022,"emailAddress":"test@iohk.io"}""" + val descriptorPath = + DescriptorPath(Json.encodeToJsonElement(Json.parseToJsonElement(claims))) + val path = "/emailAddress" + val value = descriptorPath.getValue(path) + assertNotNull(value) + assertEquals("test@iohk.io", value) + } +} \ No newline at end of file diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index dd1477eaf..3a0a7bdd8 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -44,6 +44,7 @@ import org.hyperledger.identus.walletsdk.domain.models.JWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.JWTVerifiableCredential import org.hyperledger.identus.walletsdk.domain.models.KeyCurve import org.hyperledger.identus.walletsdk.domain.models.KeyValue +import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.PresentationClaims import org.hyperledger.identus.walletsdk.domain.models.RequestedAttributes @@ -53,6 +54,7 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.AnoncredsPresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.JWTPresentationOptions import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT +import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsSDJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential @@ -1197,40 +1199,45 @@ class PolluxImplTest { type = CredentialType.SDJWT, presentationClaims = JWTPresentationClaims( claims = mapOf( - "familyName" to InputFieldFilter( + "first_name" to InputFieldFilter( type = "string", pattern = "Wonderland" ), - "givenName" to InputFieldFilter( + "last_name" to InputFieldFilter( type = "string", pattern = "Alice" ), - "drivingClass" to InputFieldFilter( - type = "integer", - pattern = "3" - ), - "dateOfIssuance" to InputFieldFilter( - type = "string", - pattern = "2020-11-13T20:20:39+00:00" - ), "emailAddress" to InputFieldFilter( type = "string", pattern = "alice@wonderland.com" ), - "drivingLicenseID" to InputFieldFilter( - type = "string", - pattern = "12345" - ) ) ), - options = SDJWTPresentationOptions( - presentationFrame = emptyMap() - ) + options = SDJWTPresentationOptions() ) // TODO: Validate SD-JWT specific fields } + @Test + fun `test verifyPresentationSubmission SD-JWT`() = runTest { + pollux = PolluxImpl(apollo, castor, api) + val msg = Json.decodeFromString( + """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjVhMTljNjZmLWEwZTUtNGFhOC1iNmE5LTdjZGIzYzk0ZGI0NyIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjBjNGY1ZDg0LTBmYjItNGU0Yy1iMDViLTZjZTBmMGFmYzU4NiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2SyJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZpcnN0X25hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZpcnN0X25hbWUiLCIkLmZpcnN0X25hbWUiXSwiaWQiOiI2YWFmYmI3NC01ZjE4LTQ4YTMtOGJlNC00MmI3NWIzZmNlZDgiLCJuYW1lIjoiZmlyc3RfbmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IkNyaXN0aWFuIn19LHsicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0Lmxhc3RfbmFtZSIsIiQuY3JlZGVudGlhbFN1YmplY3QubGFzdF9uYW1lIiwiJC5sYXN0X25hbWUiXSwiaWQiOiJiMmU1NTkxNi0yYjU1LTQ0MTEtYTQzMS0wOTlkOWMyMDQ0ZjAiLCJuYW1lIjoibGFzdF9uYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiR29uemFsZXoifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZW1haWxBZGRyZXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmVtYWlsQWRkcmVzcyJdLCJpZCI6IjU2ODRlMTY0LTA0MzAtNGYyYS1iMDI2LWRmNjcwYWZjNGVkNSIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJ0ZXN0QGlvaGsuaW8ifX1dLCJsaW1pdF9kaXNjbG9zdXJlIjoicmVxdWlyZWQifX1dLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2SyJdfX19fQ=="},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" + ) + val requestData = msg.attachments.first().data.getDataAsJsonString() + + val presentationSubmission = + """{"presentation_submission":{"id":"780d0597-b2fd-4f22-a7dc-b29b4fa08cc1","definition_id":"5569abd7-2994-4068-be36-4025ebcaf20b","descriptor_map":[{"id":"0c4f5d84-0fb2-4e4c-b05b-6ce0f0afc586","format":"sdjwt","path":"${'$'}.verifiablePresentation[0]"}]},"verifiablePresentation":["eyJhbGciOiJFZERTQSJ9.eyJzdWIiOiJkaWQ6cHJpc206YXNkZmFzZGYiLCJfc2QiOlsiWWdQSV9kREQySVE4Z3d0MF9CbUs4Rk55MEpVVXoxbmJoTzQ2b0tuNUFsRSIsIi1nZ2wxR3g1akQzY2VoNHUxTHRXc0xQM3Z5MWtGNktlN2lCWXV5Ynd2eUUiLCJEQnM2Z0hhQTZZUldDSGVXZjlnRHA2eXVnaTVtX3dudkdPSno0aFdmdXlNIl0sIl9zZF9hbGciOiJzaGEtMjU2IiwiaXNzIjoiZGlkOnByaXNtOmNlMzQwM2I1YTczMzg4MzAzNWQ2ZWM0M2JhMDc1YTQxYzljYzBhMzI1Nzk3N2Q4MGM3NWQ2MzE5YWRlMGVkNzAiLCJleHAiOjE3MzU2ODk2NjEsImlhdCI6MTUxNjIzOTAyMn0.hVy9qpC9bVFpoGQ0G2s1GOkDgZxNGdhHIoVOaF8IMQU5pIvxZH1UQ1GzDpnhU0m_ZM8r1QpCeSVPuQKvMluDBQ~WyJtRzVscURxRFg2YWdraHpjX0NBR19RIiwiZmlyc3RfbmFtZSIsIkNyaXN0aWFuIl0~WyJSY2Z5X3ZCQm9wUjhLZFZqejRxbGJRIiwibGFzdF9uYW1lIiwiR29uemFsZXoiXQ~WyJpM0kzQWZJTERuSnVqRXkxS2ZNbFp3IiwiZW1haWxBZGRyZXNzIiwidGVzdEBpb2hrLmlvIl0~"]}""" + + assertTrue( + pollux.verifyPresentationSubmission( + presentationSubmission, + PresentationSubmissionOptionsSDJWT(presentationDefinitionRequest = requestData) + ) + ) + } + private suspend fun createVerificationTestCase(testCaseOptions: VerificationTestCase): Triple { val currentDate = Calendar.getInstance() val nextMonthDate = currentDate.clone() as Calendar diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredentialTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredentialTest.kt new file mode 100644 index 000000000..c1abc53c3 --- /dev/null +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredentialTest.kt @@ -0,0 +1,104 @@ +package org.hyperledger.identus.walletsdk.pollux.models + +import com.nimbusds.jose.JWSAlgorithm +import com.nimbusds.jose.crypto.Ed25519Signer +import com.nimbusds.jose.jwk.KeyUse +import com.nimbusds.jose.jwk.OctetKeyPair +import com.nimbusds.jose.util.Base64URL +import com.nimbusds.jwt.SignedJWT +import eu.europa.ec.eudi.sdjwt.SdJwtIssuer +import eu.europa.ec.eudi.sdjwt.exp +import eu.europa.ec.eudi.sdjwt.iat +import eu.europa.ec.eudi.sdjwt.iss +import eu.europa.ec.eudi.sdjwt.nimbus +import eu.europa.ec.eudi.sdjwt.plain +import eu.europa.ec.eudi.sdjwt.sd +import eu.europa.ec.eudi.sdjwt.sdJwt +import eu.europa.ec.eudi.sdjwt.serialize +import eu.europa.ec.eudi.sdjwt.sub +import junit.framework.TestCase.assertTrue +import kotlinx.coroutines.test.runTest +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.put +import org.hyperledger.identus.apollo.base64.base64UrlDecoded +import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair +import org.hyperledger.identus.walletsdk.domain.models.CredentialOperationsOptions +import org.hyperledger.identus.walletsdk.domain.models.CredentialType +import org.hyperledger.identus.walletsdk.domain.models.DID +import org.hyperledger.identus.walletsdk.domain.models.Message +import org.junit.Test + +class SDJWTCredentialTest { + + var issuer: SdJwtIssuer? = null + + @Test + fun `test credential presentation`() = runTest { + val keyPair = Ed25519KeyPair.generateKeyPair() + + val credential = createSDJWTCredential(keyPair) + + val msg = Json.decodeFromString( + """{"id":"b3c7e130-9e90-454a-945c-58c7a4c9a181","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1727207690","expires_time_plus":"1727294090","attachments":[{"id":"6447a63c-17fa-4e22-a27a-ec0f6d5d753f","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjU1NjlhYmQ3LTI5OTQtNDA2OC1iZTM2LTQwMjVlYmNhZjIwYiIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjU1ZmNhNzJlLWYyODQtNDNlZC1iMmNjLTNlODVmNzIwNmJjMSIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZpcnN0X25hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZpcnN0X25hbWUiLCIkLmZpcnN0X25hbWUiXSwiaWQiOiIyNzVmNDcyYi0wNWE2LTRhNzAtODcxMS03NDVkODM1ZDlkZDUiLCJuYW1lIjoiZmlyc3RfbmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QubGFzdF9uYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5sYXN0X25hbWUiLCIkLmxhc3RfbmFtZSJdLCJpZCI6IjA0YjhjMjMxLTE1YjQtNDI1NC1hNDRmLWZjMmM2NzAzMmE0MCIsIm5hbWUiOiJsYXN0X25hbWUiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJBbGljZSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyIsIiQuZW1haWxBZGRyZXNzIl0sImlkIjoiYmQxMWVlMjAtZDcyNS00ZjA2LWJmMWItNjE3NDFiMmZmM2FmIiwibmFtZSI6ImVtYWlsQWRkcmVzcyIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6ImFsaWNlQHdvbmRlcmxhbmQuY29tIn19XSwibGltaXRfZGlzY2xvc3VyZSI6InJlcXVpcmVkIn19XSwiZm9ybWF0Ijp7InNkSnd0Ijp7ImFsZyI6WyJFUzI1NmsiXX19fX0"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"23f89714-7325-4048-a26b-978a0615efd7","ack":[],"direction":"SENT"}""" + ) + val requestData = msg.attachments.first().data.getDataAsJsonString() + + val presentation = credential.presentation( + attachmentFormat = CredentialType.SDJWT.type, + request = requestData.encodeToByteArray(), + listOf(CredentialOperationsOptions.DisclosingClaims(listOf("/emailAddress", "/first_name", "/last_name"))) + ) + val presentationSubmission = Json.decodeFromString(presentation) + assertTrue(presentationSubmission.verifiablePresentation.first().contains(".")) + val jws = presentationSubmission.verifiablePresentation.first() + val disclosures = jws.split("~") + var foundFirstName = false + var foundLastName = false + var foundEmail = false + for (i in 1..disclosures.size - 2) { + val value = disclosures[i].base64UrlDecoded + if (value.contains("first_name")) { + foundFirstName = true + } + if (value.contains("last_name")) { + foundLastName = true + } + if (value.contains("email")) { + foundEmail = true + } + } + assertTrue(foundFirstName && foundLastName && foundEmail) + } + + fun createSDJWTCredential(keyPair: Ed25519KeyPair): SDJWTCredential { + val subject = DID("did:prism:asdfasdf") + + val octet = OctetKeyPair.Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(keyPair.publicKey.raw)) + .d(Base64URL.encode(keyPair.privateKey.raw)) + .keyUse(KeyUse.SIGNATURE) + .build() + + issuer = SdJwtIssuer + .nimbus( + signer = Ed25519Signer(octet), + signAlgorithm = JWSAlgorithm.EdDSA + ) + val sdjwt = issuer!!.issue( + sdJwt { + plain { + sub(subject.toString()) + iss("did:prism:ce3403b5a733883035d6ec43ba075a41c9cc0a3257977d80c75d6319ade0ed70") + iat(1516239022) + exp(1735689661) + } + sd { + put("first_name", "Cristian") + put("last_name", "Gonzalez") + put("emailAddress", "test@iohk.io") + } + } + ).getOrThrow().serialize() + + return SDJWTCredential.fromSDJwtString(sdjwt) + } +} diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt index 2f3c00ad9..9df94c718 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt @@ -33,7 +33,7 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat fun createSdJwt() { viewModelScope.launch { - Sdk.getInstance().agent.createSDJWTCredential() +// Sdk.getInstance().agent.createSDJWTCredential() } } } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index 65078ba41..b0c821953 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -23,6 +23,7 @@ import org.hyperledger.identus.walletsdk.domain.models.InputFieldFilter import org.hyperledger.identus.walletsdk.domain.models.JWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential +import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.edgeagent.DIDCOMM1 import org.hyperledger.identus.walletsdk.edgeagent.EdgeAgentError import org.hyperledger.identus.walletsdk.edgeagent.protocols.ProtocolType @@ -117,7 +118,7 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application sdk.agent.initiatePresentationRequest( type = CredentialType.SDJWT, toDID = DID(toDID), - presentationClaims = JWTPresentationClaims( + presentationClaims = SDJWTPresentationClaims( claims = mapOf( "familyName" to InputFieldFilter( type = "string", @@ -144,9 +145,7 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application pattern = "12345" ) ) - ), - domain = "domain", - challenge = "challenge" + ) ) // Anoncreds presentation request From 877e9482ddda8e51feb545d0abd3826015a67a98 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 12:30:17 -0400 Subject: [PATCH 08/22] refactor: clean up code, remove deprecated unit tests Signed-off-by: Cristian G --- .../walletsdk/castor/shared/CastorShared.kt | 2 +- .../domain/models/ProvableCredential.kt | 4 +- .../models/keyManagement/VerifiableKey.kt | 2 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 123 +++------ .../walletsdk/edgeagent/EdgeAgentError.kt | 11 + .../PreparePresentationOptions.kt | 2 +- .../walletsdk/pollux/DescriptorPath.kt | 3 - .../identus/walletsdk/pollux/PolluxImpl.kt | 239 +++++++----------- .../pollux/models/SDJWTCredential.kt | 2 +- .../walletsdk/pollux/DescriptorPathTest.kt | 2 +- .../walletsdk/pollux/PolluxImplTest.kt | 161 +++--------- .../ui/credentials/CredentialsFragment.kt | 1 - .../ui/credentials/CredentialsViewModel.kt | 6 - .../main/res/layout/fragment_credentials.xml | 12 - 14 files changed, 174 insertions(+), 396 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt index a51321c9f..bfc976a57 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt @@ -439,7 +439,7 @@ internal class CastorShared { * @return [OctetPublicKey]. */ @JvmStatic - internal fun octetPublicKey(keyPair: KeyPair): OctetPublicKey { + private fun octetPublicKey(keyPair: KeyPair): OctetPublicKey { val curve = when (keyPair::class) { Secp256k1KeyPair::class -> { Curve.SECP256K1 diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt index 5b20aa7ba..034a10265 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/ProvableCredential.kt @@ -1,7 +1,7 @@ package org.hyperledger.identus.walletsdk.domain.models import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey as SignableKeyModel sealed class CredentialOperationsOptions { data class Schema(val id: String, val json: String) : CredentialOperationsOptions() @@ -11,7 +11,7 @@ sealed class CredentialOperationsOptions { data class LinkSecret(val id: String, val secret: String) : CredentialOperationsOptions() data class SubjectDID(val did: DID) : CredentialOperationsOptions() data class Entropy(val entropy: String) : CredentialOperationsOptions() - data class SignableKey(val key: org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey?) : CredentialOperationsOptions() + data class SignableKey(val key: SignableKeyModel?) : CredentialOperationsOptions() data class ExportableKey(val key: PrivateKey?) : CredentialOperationsOptions() data class ZkpPresentationParams(val attributes: Map, val predicates: List) : CredentialOperationsOptions() diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt index 1e3184769..081445537 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/VerifiableKey.kt @@ -11,4 +11,4 @@ interface VerifiableKey { * @return a boolean which tell us if message and signature match */ fun verify(message: ByteArray, signature: ByteArray): Boolean -} \ No newline at end of file +} diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 7b92cdd6c..cd7383fcc 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -11,30 +11,12 @@ import com.nimbusds.jose.JWEDecrypter import com.nimbusds.jose.JWEEncrypter import com.nimbusds.jose.JWEHeader import com.nimbusds.jose.JWEObject -import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.Payload -import com.nimbusds.jose.crypto.ECDSAVerifier -import com.nimbusds.jose.crypto.Ed25519Signer import com.nimbusds.jose.crypto.X25519Decrypter import com.nimbusds.jose.crypto.X25519Encrypter -import com.nimbusds.jose.jwk.KeyUse import com.nimbusds.jose.jwk.OctetKeyPair -import com.nimbusds.jose.jwk.gen.ECKeyGenerator import com.nimbusds.jose.util.Base64URL -import eu.europa.ec.eudi.sdjwt.JwtAndClaims -import eu.europa.ec.eudi.sdjwt.SdJwt -import eu.europa.ec.eudi.sdjwt.SdJwtIssuer -import eu.europa.ec.eudi.sdjwt.SdJwtVerifier -import eu.europa.ec.eudi.sdjwt.asJwtVerifier -import eu.europa.ec.eudi.sdjwt.exp -import eu.europa.ec.eudi.sdjwt.iat -import eu.europa.ec.eudi.sdjwt.iss -import eu.europa.ec.eudi.sdjwt.nimbus -import eu.europa.ec.eudi.sdjwt.plain -import eu.europa.ec.eudi.sdjwt.sd -import eu.europa.ec.eudi.sdjwt.sdJwt import eu.europa.ec.eudi.sdjwt.serialize -import eu.europa.ec.eudi.sdjwt.sub import eu.europa.ec.eudi.sdjwt.vc.SD_JWT_VC_TYPE import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.ContentType @@ -44,8 +26,6 @@ import io.ktor.serialization.kotlinx.json.json import io.ktor.util.date.getTimeMillis import java.net.UnknownHostException import java.security.SecureRandom -import java.security.interfaces.ECPrivateKey -import java.security.interfaces.ECPublicKey import java.util.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -56,7 +36,6 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.datetime.Instant import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException @@ -66,7 +45,6 @@ import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey @@ -100,7 +78,6 @@ import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.PresentationClaims import org.hyperledger.identus.walletsdk.domain.models.PrismDIDInfo import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential -import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.Signature import org.hyperledger.identus.walletsdk.domain.models.UnknownError @@ -1144,7 +1121,10 @@ open class EdgeAgent { } if (preparePresentationOptions !is SDJWTPreparePresentationOptions) { - throw Exception() // TODO: Custom exception + throw EdgeAgentError.InvalidPresentationOptions( + preparePresentationOptions::class.java.name, + SDJWTPreparePresentationOptions::class.java.name + ) } val requestData = request.attachments.firstNotNullOf { @@ -1204,33 +1184,39 @@ open class EdgeAgent { val presentationDefinitionRequest: String val attachmentDescriptor: AttachmentDescriptor when (type) { - CredentialType.JWT, - CredentialType.SDJWT -> { - val options = if (type == CredentialType.JWT) { - if (domain == null) { - throw EdgeAgentError.MissingOrNullFieldError("Domain", "initiatePresentationRequest parameters") - } - if (challenge == null) { - throw EdgeAgentError.MissingOrNullFieldError( - "Challenge", - "initiatePresentationRequest parameters" - ) - } - JWTPresentationOptions( + CredentialType.JWT -> { + if (domain == null) { + throw EdgeAgentError.MissingOrNullFieldError("Domain", "initiatePresentationRequest parameters") + } + if (challenge == null) { + throw EdgeAgentError.MissingOrNullFieldError( + "Challenge", + "initiatePresentationRequest parameters" + ) + } + presentationDefinitionRequest = pollux.createPresentationDefinitionRequest( + type = type, + presentationClaims = presentationClaims, + options = JWTPresentationOptions( jwt = arrayOf("ES256K"), domain = domain, challenge = challenge ) - } else { - SDJWTPresentationOptions( - sdjwt = arrayOf("ES256k") - ) - } + ) + attachmentDescriptor = AttachmentDescriptor( + mediaType = "application/json", + format = CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + data = AttachmentBase64(presentationDefinitionRequest.base64UrlEncoded) + ) + } + CredentialType.SDJWT -> { presentationDefinitionRequest = pollux.createPresentationDefinitionRequest( type = type, presentationClaims = presentationClaims, - options = options + options = SDJWTPresentationOptions( + sdjwt = arrayOf("ES256k") + ) ) attachmentDescriptor = AttachmentDescriptor( mediaType = "application/json", @@ -1321,7 +1307,6 @@ open class EdgeAgent { data = AttachmentBase64(presentationString.base64UrlEncoded) ) - val fromDID = requestPresentation.to ?: createNewPeerDID(updateMediator = true) return Presentation( body = Presentation.Body(), attachments = arrayOf(attachmentDescriptor), @@ -1342,7 +1327,10 @@ open class EdgeAgent { } if (preparePresentationOptions !is SDJWTPreparePresentationOptions) { - throw Exception() // TODO: Custom exception + throw EdgeAgentError.InvalidPresentationOptions( + preparePresentationOptions::class.java.name, + SDJWTPreparePresentationOptions::class.java.name + ) } val didString = @@ -1411,11 +1399,10 @@ open class EdgeAgent { } } } + throw EdgeAgentError.CredentialNotValidForPresentationRequest() } catch (e: Exception) { throw EdgeAgentError.CredentialNotValidForPresentationRequest() } - // TODO: handle this point - throw EdgeAgentError.CredentialNotValidForPresentationRequest() } suspend fun handlePresentation(msg: Message): Boolean { @@ -1596,48 +1583,6 @@ open class EdgeAgent { } } - // TODO: Remove before merging - suspend fun createSDJWTCredential(keyPair: Ed25519KeyPair): SDJWTCredential { - val subject = createNewPrismDID() - - val octet = OctetKeyPair.Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(keyPair.publicKey.raw)) - .d(Base64URL.encode(keyPair.privateKey.raw)) - .keyUse(KeyUse.SIGNATURE) - .build() - - val issuer = SdJwtIssuer - .nimbus( - signer = Ed25519Signer(octet), - signAlgorithm = JWSAlgorithm.EdDSA - ) - val sdjwt = issuer.issue( - sdJwt { - plain { - sub(subject.toString()) - iss("did:prism:ce3403b5a733883035d6ec43ba075a41c9cc0a3257977d80c75d6319ade0ed70") - iat(1516239022) - exp(1735689661) - } - sd { - put("first_name", "Cristian") - put("last_name", "Gonzalez") - put("emailAddress", "test@iohk.io") - } - } - ).getOrThrow().serialize() - - val verified: SdJwt.Issuance = runBlocking { - SdJwtVerifier.verifyIssuance( - jwtSignatureVerifier = ECDSAVerifier(keyPair.publicKey.jca() as ECPublicKey).asJwtVerifier(), - unverifiedSdJwt = sdjwt - ).getOrThrow() - } - - val cred = SDJWTCredential.fromSDJwtString(sdjwt) - pluto.storeCredential(cred.toStorableCredential()) - return cred - } - /** * This method retrieves the link secret from Pluto. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt index afce6e5c2..6b3f71ff6 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt @@ -4,6 +4,8 @@ import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.Error import org.hyperledger.identus.walletsdk.domain.models.KnownPrismError +import kotlin.reflect.KClass +import kotlin.reflect.jvm.internal.impl.load.java.structure.JavaClass sealed class EdgeAgentError : KnownPrismError() { @@ -152,4 +154,13 @@ sealed class EdgeAgentError : KnownPrismError() { override val message: String get() = "This invitation has expired." } + + class InvalidPresentationOptions(private val type: String, private val expectedType: String) : + EdgeAgentError() { + override val code: Int + get() = 616 + + override val message: String + get() = "Invalid presentation, got $type but expected $expectedType" + } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt index 4f992946e..bac7144b3 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/protocols/proofOfPresentation/PreparePresentationOptions.kt @@ -4,4 +4,4 @@ sealed interface PreparePresentationOptions data class SDJWTPreparePresentationOptions( val presentationFrame: Map -) : PreparePresentationOptions \ No newline at end of file +) : PreparePresentationOptions diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt index 16bc15c74..3064b62b6 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt @@ -23,9 +23,6 @@ class DescriptorPath(private val jsonElement: JsonElement) { // Drop the first segment if it starts with "$" val filteredSegments = if (path.startsWith("$")) segments.drop(1) else segments - - -// val segments = path.split(regex).filter { it.isNotEmpty() }.drop(1) val jsonObject = jsonElement.jsonObject var current: Any? = jsonObject diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index ecf880217..4b98b69f1 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -764,12 +764,11 @@ open class PolluxImpl( presentationClaims: PresentationClaims, options: PresentationOptions ): String { - val format: PresentationDefinition.InputDescriptor.PresentationFormat val inputDescriptor: PresentationDefinition.InputDescriptor when (type) { CredentialType.JWT -> { if (options.name == null || options.purpose == null) { - throw Exception("") + throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation options must contain name and purpose") } if (options is JWTPresentationOptions) { if (presentationClaims !is JWTPresentationClaims) { @@ -845,7 +844,7 @@ open class PolluxImpl( CredentialType.SDJWT -> { if (options.name == null || options.purpose == null) { - throw Exception("") + throw PolluxError.PresentationDefinitionRequestError("When type is ${type.type}, presentation options must contain name and purpose") } if (options is SDJWTPresentationOptions) { if (presentationClaims !is SDJWTPresentationClaims) { @@ -935,13 +934,15 @@ open class PolluxImpl( } else if (inputFilter.lte != null) { "<=" } else { - throw Exception() // TODO: Custom exception + throw PolluxError.PresentationDefinitionRequestError("Input filter must contain at one of the following gt, gte, lt, lte as not null") } - val pValue = (inputFilter.gt - ?: (inputFilter.gte - ?: (inputFilter.lt - ?: (inputFilter.lte ?: throw Exception())))) as Int // TODO: Custom excetion + val pValue = + (inputFilter.gt + ?: (inputFilter.gte + ?: (inputFilter.lt + ?: (inputFilter.lte + ?: throw PolluxError.PresentationDefinitionRequestError("Input filter must contain at one of the following gt, gte, lt, lte as not null"))))) as Int // Based on the definition of AnoncredsPresentationClaims we do not need to verify if key is duplicated. mapPredicate[key] = RequestedPredicates( @@ -977,7 +978,11 @@ open class PolluxImpl( ) } - // TODO: Deprecate, the new way to create JWT presentation submission is using the credential. ProvableCredential.presentation() + @Deprecated( + "This method is deprecated, this functionality was moved to each credential", + ReplaceWith("ProvableCredential.presentation(attachmentFormat, request, options)"), + DeprecationLevel.ERROR + ) override suspend fun createJWTPresentationSubmission( presentationDefinitionRequest: String, credential: Credential, @@ -1042,6 +1047,11 @@ open class PolluxImpl( } ?: throw PolluxError.NonNullableError("CredentialSubject") } + @Deprecated( + "This method is deprecated, this functionality was moved to each credential", + ReplaceWith("ProvableCredential.presentation(attachmentFormat, request, options)"), + DeprecationLevel.ERROR + ) override suspend fun createAnoncredsPresentationSubmission( presentationDefinitionRequest: String, credential: Credential, @@ -1164,8 +1174,7 @@ open class PolluxImpl( PresentationSubmission(submission, arrayStrings) } ?: throw PolluxError.VerificationUnsuccessful("Presentation is missing presentation_submission") - val presentationDefinitionRequestString = - (options as PresentationSubmissionOptionsJWT).presentationDefinitionRequest + val presentationDefinitionRequestString = options.presentationDefinitionRequest val presentationDefinitionRequest = Json.decodeFromString(presentationDefinitionRequestString) @@ -1235,84 +1244,10 @@ open class PolluxImpl( } // Now we are going to validate the requested fields with the provided credentials - val verifiableCredentialDescriptorPath = - DescriptorPath(Json.encodeToJsonElement(verifiableCredential)) val inputDescriptor = inputDescriptors.find { it.id == descriptorItem.id } - if (inputDescriptor != null) { - val constraints = inputDescriptor.constraints - val fields = constraints.fields - if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { - fields?.forEach { field -> - val optional = field.optional - if (!optional) { - var validClaim = false - var reason = "" - val paths = field.path - paths.forEach { path -> - val fieldValue = - verifiableCredentialDescriptorPath.getValue(path) - if (fieldValue != null) { - if (field.filter != null) { - val filter: InputFieldFilter = field.filter - filter.pattern?.let { pattern -> - val regexPattern = Regex(pattern) - if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { - validClaim = true - return@forEach - } else { - reason = - "Expected the $path field to be $pattern but got $fieldValue" - } - } - filter.enum?.let { enum -> - enum.forEach { predicate -> - if (fieldValue == predicate) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" - } - } - filter.const?.let { const -> - const.forEach { constValue -> - if (fieldValue == constValue) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" - } - } - filter.value?.let { value -> - if (value == fieldValue) { - validClaim = true - return@forEach - } else { - reason = - "Expected the $path field to be $value but got $fieldValue" - } - } - } else { - reason = - "Input field filter for ${field.name} is null" - } - } else { - reason = "Field value for path $path is null" - } - } - if (!validClaim) { - throw PolluxError.VerificationUnsuccessful(reason) - } - } - } - } - } + verifyInputDescriptors(inputDescriptor, Json.encodeToJsonElement(verifiableCredential)) + } ?: throw PolluxError.VerificationUnsuccessful("Invalid submission, no value found for $pathNested") return true } @@ -1382,90 +1317,96 @@ open class PolluxImpl( val presentation = SDJWTCredential.fromSDJwtString(jws) - val all = presentation.sdjwt.recreateClaimsAndDisclosuresPerClaim { it.second } - val claimsAndDisclosures = DescriptorPath(all.first.toJsonElement()) - + val claimsAndDisclosures = presentation.sdjwt.recreateClaimsAndDisclosuresPerClaim { it.second } val inputDescriptors = presentationDefinitionRequest.presentationDefinition.inputDescriptors val inputDescriptor = inputDescriptors.find { it.id == descriptorItem.id } - if (inputDescriptor != null) { - val constraints = inputDescriptor.constraints - val fields = constraints.fields - if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { - fields?.forEach { field -> - val optional = field.optional - if (!optional) { - var validClaim = false - var reason = "" - val paths = field.path - paths.forEach { path -> - val fieldValue = - claimsAndDisclosures.getValue(path) - if (fieldValue != null) { - if (field.filter != null) { - val filter: InputFieldFilter = field.filter - filter.pattern?.let { pattern -> - val regexPattern = Regex(pattern) - if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { + verifyInputDescriptors(inputDescriptor, claimsAndDisclosures.first.toJsonElement()) + return true + } + return false + } + + private fun verifyInputDescriptors( + inputDescriptor: PresentationDefinition.InputDescriptor?, + descriptorPath: JsonElement + ) { + val claimsAndDisclosures = DescriptorPath(descriptorPath) + if (inputDescriptor != null) { + val constraints = inputDescriptor.constraints + val fields = constraints.fields + if (constraints.limitDisclosure == PresentationDefinition.InputDescriptor.Constraints.LimitDisclosure.REQUIRED) { + fields?.forEach { field -> + val optional = field.optional + if (!optional) { + var validClaim = false + var reason = "" + val paths = field.path + paths.forEach { path -> + val fieldValue = + claimsAndDisclosures.getValue(path) + if (fieldValue != null) { + if (field.filter != null) { + val filter: InputFieldFilter = field.filter + filter.pattern?.let { pattern -> + val regexPattern = Regex(pattern) + if (regexPattern.matches(fieldValue.toString()) || fieldValue == pattern) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $pattern but got $fieldValue" + } + } + filter.enum?.let { enum -> + enum.forEach { predicate -> + if (fieldValue == predicate) { validClaim = true return@forEach - } else { - reason = - "Expected the $path field to be $pattern but got $fieldValue" } } - filter.enum?.let { enum -> - enum.forEach { predicate -> - if (fieldValue == predicate) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" - } + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.enum.joinToString { ", " }} but got $fieldValue" } - filter.const?.let { const -> - const.forEach { constValue -> - if (fieldValue == constValue) { - validClaim = true - return@forEach - } - } - if (!validClaim) { - reason = - "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" - } - } - filter.value?.let { value -> - if (value == fieldValue) { + } + filter.const?.let { const -> + const.forEach { constValue -> + if (fieldValue == constValue) { validClaim = true return@forEach - } else { - reason = - "Expected the $path field to be $value but got $fieldValue" } } - } else { - reason = - "Input field filter for ${field.name} is null" + if (!validClaim) { + reason = + "Expected the $path field to be one of ${filter.const.joinToString { ", " }} but got $fieldValue" + } + } + filter.value?.let { value -> + if (value == fieldValue) { + validClaim = true + return@forEach + } else { + reason = + "Expected the $path field to be $value but got $fieldValue" + } } } else { - reason = "Field value for path $path is null" + reason = + "Input field filter for ${field.name} is null" } + } else { + reason = "Field value for path $path is null" } - if (!validClaim) { - throw PolluxError.VerificationUnsuccessful(reason) - } + } + if (!validClaim) { + throw PolluxError.VerificationUnsuccessful(reason) } } } } - return true } - return false } private suspend fun verifyAnoncredsPresentation( diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index 91c931329..3d1ae2352 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -60,7 +60,7 @@ data class SDJWTCredential( @Transient override val issuer: String = sdjwt.jwt.second["iss"]?.jsonPrimitive?.content - ?: throw Exception("Most contain issuer") // TODO: Custom exception + ?: throw PolluxError.InvalidJWTCredential("SD-JWT must contain issuer") override val subject: String? get() = sdjwt.jwt.second["sub"]?.jsonPrimitive?.content diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt index 1c4500d20..400911e62 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPathTest.kt @@ -34,4 +34,4 @@ class DescriptorPathTest { assertNotNull(value) assertEquals("test@iohk.io", value) } -} \ No newline at end of file +} diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 3a0a7bdd8..56b13b22d 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -35,6 +35,7 @@ import org.hyperledger.identus.walletsdk.domain.models.AnoncredsInputFieldFilter import org.hyperledger.identus.walletsdk.domain.models.AnoncredsPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Api import org.hyperledger.identus.walletsdk.domain.models.ApiImpl +import org.hyperledger.identus.walletsdk.domain.models.CredentialOperationsOptions import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.Curve import org.hyperledger.identus.walletsdk.domain.models.DID @@ -48,6 +49,7 @@ import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.PresentationClaims import org.hyperledger.identus.walletsdk.domain.models.RequestedAttributes +import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.httpClient import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey @@ -62,6 +64,7 @@ import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefi import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers.anyString @@ -213,126 +216,6 @@ class PolluxImplTest { ) } - @Test - fun testCreatePresentationSubmission_whenCredentialNotJWT_thenExceptionThrown() = runTest { - val definitionJson = """ - { - "presentation_definition": { - "id": "32f54163-7166-48f1-93d8-ff217bdb0653", - "input_descriptors": [ - { - "id": "wa_driver_license", - "name": "Washington State Business License", - "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", - "constraints": { - "fields": [ - { - "path": [ - "$.credentialSubject.dateOfBirth", - "$.credentialSubject.dob", - "$.vc.credentialSubject.dateOfBirth", - "$.vc.credentialSubject.dob" - ] - } - ] - } - } - ], - "format": { - "jwt": { - "alg": ["ES256K"] - } - } - }, - "options": { - "domain": "domain", - "challenge": "challenge" - } - } - """ - - val presentationDefinitionRequest = definitionJson - - val credential = AnonCredential( - schemaID = "", - credentialDefinitionID = "", - values = mapOf(), - signatureJson = "", - signatureCorrectnessProofJson = "", - revocationRegistryId = null, - revocationRegistryJson = null, - witnessJson = "", - json = "" - ) - val secpKeyPair = generateSecp256k1KeyPair() - - pollux = PolluxImpl(apollo, castor, api) - - assertFailsWith(PolluxError.CredentialTypeNotSupportedError::class) { - pollux.createJWTPresentationSubmission( - presentationDefinitionRequest = presentationDefinitionRequest, - credential = credential, - privateKey = secpKeyPair.privateKey - ) - } - } - - @Test - fun testCreatePresentationSubmission_whenPrivateKeyNotSecp256k1_thenExceptionThrown() = - runTest { - val definitionJson = """ - { - "presentation_definition": { - "id": "32f54163-7166-48f1-93d8-ff217bdb0653", - "input_descriptors": [ - { - "id": "wa_driver_license", - "name": "Washington State Business License", - "purpose": "We can only allow licensed Washington State business representatives into the WA Business Conference", - "constraints": { - "fields": [ - { - "path": [ - "$.credentialSubject.dateOfBirth", - "$.credentialSubject.dob", - "$.vc.credentialSubject.dateOfBirth", - "$.vc.credentialSubject.dob" - ] - } - ] - } - } - ], - "format": { - "jwt": { - "alg": ["ES256K"] - } - } - }, - "options": { - "domain": "domain", - "challenge": "challenge" - } - } - """ - - val presentationDefinitionRequest = definitionJson - val credential = JWTCredential.fromJwtString( - "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206MjU3MTlhOTZiMTUxMjA3MTY5ODFhODQzMGFkMGNiOTY4ZGQ1MzQwNzM1OTNjOGNkM2YxZDI3YTY4MDRlYzUwZTpDcG9DQ3BjQ0Vsb0tCV3RsZVMweEVBSkNUd29KYzJWamNESTFObXN4RWlBRW9TQ241dHlEYTZZNnItSW1TcXBKOFkxbWo3SkMzX29VekUwTnl5RWlDQm9nc2dOYWVSZGNDUkdQbGU4MlZ2OXRKZk53bDZyZzZWY2hSM09xaGlWYlRhOFNXd29HWVhWMGFDMHhFQVJDVHdvSmMyVmpjREkxTm1zeEVpRE1rQmQ2RnRpb0prM1hPRnUtX2N5NVhtUi00dFVRMk5MR2lXOGFJU29ta1JvZzZTZGU5UHduRzBRMFNCVG1GU1REYlNLQnZJVjZDVExYcmpJSnR0ZUdJbUFTWEFvSGJXRnpkR1Z5TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTzcxMG10MVdfaXhEeVFNM3hJczdUcGpMQ05PRFF4Z1ZoeDVzaGZLTlgxb2FJSFdQcnc3SVVLbGZpYlF0eDZKazRUU2pnY1dOT2ZjT3RVOUQ5UHVaN1Q5dCIsInN1YiI6ImRpZDpwcmlzbTpiZWVhNTIzNGFmNDY4MDQ3MTRkOGVhOGVjNzdiNjZjYzdmM2U4MTVjNjhhYmI0NzVmMjU0Y2Y5YzMwNjI2NzYzOkNzY0JDc1FCRW1RS0QyRjFkR2hsYm5ScFkyRjBhVzl1TUJBRVFrOEtDWE5sWTNBeU5UWnJNUklnZVNnLTJPTzFKZG5welVPQml0eklpY1hkZnplQWNUZldBTi1ZQ2V1Q2J5SWFJSlE0R1RJMzB0YVZpd2NoVDNlMG5MWEJTNDNCNGo5amxzbEtvMlpsZFh6akVsd0tCMjFoYzNSbGNqQVFBVUpQQ2dselpXTndNalUyYXpFU0lIa29QdGpqdFNYWjZjMURnWXJjeUluRjNYODNnSEUzMWdEZm1BbnJnbThpR2lDVU9Ca3lOOUxXbFlzSElVOTN0Snkxd1V1TndlSV9ZNWJKU3FObVpYVjg0dyIsIm5iZiI6MTY4NTYzMTk5NSwiZXhwIjoxNjg1NjM1NTk1LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJhZGRpdGlvbmFsUHJvcDIiOiJUZXN0MyIsImlkIjoiZGlkOnByaXNtOmJlZWE1MjM0YWY0NjgwNDcxNGQ4ZWE4ZWM3N2I2NmNjN2YzZTgxNWM2OGFiYjQ3NWYyNTRjZjljMzA2MjY3NjM6Q3NjQkNzUUJFbVFLRDJGMWRHaGxiblJwWTJGMGFXOXVNQkFFUWs4S0NYTmxZM0F5TlRack1SSWdlU2ctMk9PMUpkbnB6VU9CaXR6SWljWGRmemVBY1RmV0FOLVlDZXVDYnlJYUlKUTRHVEkzMHRhVml3Y2hUM2UwbkxYQlM0M0I0ajlqbHNsS28yWmxkWHpqRWx3S0IyMWhjM1JsY2pBUUFVSlBDZ2x6WldOd01qVTJhekVTSUhrb1B0amp0U1haNmMxRGdZcmN5SW5GM1g4M2dIRTMxZ0RmbUFucmdtOGlHaUNVT0JreU45TFdsWXNISVU5M3RKeTF3VXVOd2VJX1k1YkpTcU5tWlhWODR3In0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiQGNvbnRleHQiOlsiaHR0cHM6XC9cL3d3dy53My5vcmdcLzIwMThcL2NyZWRlbnRpYWxzXC92MSJdfX0.x0SF17Y0VCDmt7HceOdTxfHlofsZmY18Rn6VQb0-r-k_Bm3hTi1-k2vkdjB25hdxyTCvxam-AkAP-Ag3Ahn5Ng" - ) - val nonSecpKeyPair = Ed25519KeyPair.generateKeyPair() - - pollux = PolluxImpl(apollo, castor, api) - - assertFailsWith(PolluxError.PrivateKeyTypeNotSupportedError::class) { - pollux.createJWTPresentationSubmission( - presentationDefinitionRequest = presentationDefinitionRequest, - credential = credential, - privateKey = nonSecpKeyPair.privateKey - ) - } - } - @Test fun testCreatePresentationSubmission_whenAllCorrect_thenPresentationSubmissionProofWellFormed() = runTest { @@ -1192,12 +1075,12 @@ class PolluxImplTest { } @Test - fun `Test createPresentationDefinitionRequest`() = runTest { + fun `Test SD-JWT createPresentationDefinitionRequest`() = runTest { pollux = PolluxImpl(apollo, castor, api) val sdJwtPresentationDefinitionRequest = pollux.createPresentationDefinitionRequest( type = CredentialType.SDJWT, - presentationClaims = JWTPresentationClaims( + presentationClaims = SDJWTPresentationClaims( claims = mapOf( "first_name" to InputFieldFilter( type = "string", @@ -1215,8 +1098,25 @@ class PolluxImplTest { ), options = SDJWTPresentationOptions() ) - - // TODO: Validate SD-JWT specific fields + val presentationDefinition = + Json.decodeFromString(sdJwtPresentationDefinitionRequest) + val inputDescriptor = presentationDefinition.presentationDefinition.inputDescriptors.first() + assertNotNull(inputDescriptor.constraints.fields) + var firstNameAvailable = false + var lastNameAvailable = false + var emailAvailable = false + inputDescriptor.constraints.fields!!.forEach { + if (it.name == "first_name") { + firstNameAvailable = true + } + if (it.name == "last_name") { + lastNameAvailable = true + } + if (it.name == "emailAddress") { + emailAvailable = true + } + } + assertTrue(firstNameAvailable && lastNameAvailable && emailAvailable) } @Test @@ -1303,12 +1203,15 @@ class PolluxImplTest { doReturn(false) .`when`(pollux).isCredentialRevoked(any()) - val presentationSubmission = pollux.createJWTPresentationSubmission( - presentationDefinitionRequest = presentationDefinition, - credential = jwtCredential, - privateKey = testCaseOptions.holderPrv - ) + val presentationSubmission = jwtCredential.presentation( + attachmentFormat = CredentialType.PRESENTATION_EXCHANGE_DEFINITIONS.type, + request = presentationDefinition.encodeToByteArray(), + options = listOf( + CredentialOperationsOptions.SubjectDID(testCaseOptions.holder), + CredentialOperationsOptions.ExportableKey(testCaseOptions.holderPrv) + ) + ) return Triple(presentationDefinition, presentationSubmission, jwtString) } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt index 90959ec51..d6b4b8159 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsFragment.kt @@ -29,7 +29,6 @@ class CredentialsFragment : Fragment() { } } adapter = CredentialsAdapter(itemClickListener = isRevoked) - binding.createSdjwt.setOnClickListener { viewModel.createSdJwt() } binding.credentials.adapter = adapter return binding.root } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt index 9df94c718..5d105991c 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt @@ -30,10 +30,4 @@ class CredentialsViewModel(application: Application) : AndroidViewModel(applicat Sdk.getInstance().agent.isCredentialRevoked(credential) } } - - fun createSdJwt() { - viewModelScope.launch { -// Sdk.getInstance().agent.createSDJWTCredential() - } - } } diff --git a/sampleapp/src/main/res/layout/fragment_credentials.xml b/sampleapp/src/main/res/layout/fragment_credentials.xml index 5add4678f..ec30c93a7 100644 --- a/sampleapp/src/main/res/layout/fragment_credentials.xml +++ b/sampleapp/src/main/res/layout/fragment_credentials.xml @@ -16,16 +16,4 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - From 51f4948520c60243e46c61fcf066133ac373a647 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 12:32:14 -0400 Subject: [PATCH 09/22] breaking change: remove deprecated methods Signed-off-by: Cristian G --- .../identus/walletsdk/apollo/ApolloImpl.kt | 31 ------------------- .../walletsdk/domain/buildingblocks/Pluto.kt | 13 -------- .../models/keyManagement/KeyRestoration.kt | 13 -------- .../identus/walletsdk/pluto/PlutoImpl.kt | 22 ------------- 4 files changed, 79 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt index 11c67a738..5fe04dea8 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt @@ -463,37 +463,6 @@ class ApolloImpl( } } - /** - * Restores a private key from StorablePrivateKey. - * - * @param storablePrivateKey The StorablePrivateKey to restore the key from. - * @return The restored Key object. - */ - @Deprecated( - "This method has been deprecated and should no longer be used.", - ReplaceWith("restorePrivateKey(restoreIdentifier, privateKeyData)"), - DeprecationLevel.ERROR - ) - override fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey { - return when (storablePrivateKey.restorationIdentifier) { - "secp256k1+priv" -> { - Secp256k1PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes) - } - - "ed25519+priv" -> { - Ed25519PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes) - } - - "x25519+priv" -> { - X25519PrivateKey(storablePrivateKey.data.base64UrlDecodedBytes) - } - - else -> { - throw PlutoError.InvalidRestorationIdentifier() - } - } - } - /** * Restores a private key from StorablePrivateKey. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Pluto.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Pluto.kt index 4822c13db..4dfd6e2b5 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Pluto.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Pluto.kt @@ -106,19 +106,6 @@ interface Pluto { */ fun storeLinkSecret(linkSecret: String) - /** - * Stores the metadata associated with a credential request. - * - * @param name the unique name used to retrieve the stored metadata. - * @param metadata The metadata to store. It must be an instance of [CredentialRequestMeta]. - */ - @Deprecated( - "This method has been deprecated and should no longer be used.", - ReplaceWith("storeCredentialMetadata(name, linkSecretName, json)"), - DeprecationLevel.ERROR - ) - fun storeCredentialMetadata(name: String, metadata: CredentialRequestMeta) - /** * Stores the metadata associated with a credential request. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/KeyRestoration.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/KeyRestoration.kt index ec5423288..e6f59fe2f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/KeyRestoration.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/models/keyManagement/KeyRestoration.kt @@ -44,19 +44,6 @@ interface KeyRestoration { */ fun restoreKey(key: JWK, index: Int? = null): Key - /** - * Restores a private key from StorablePrivateKey. - * - * @param storablePrivateKey The StorablePrivateKey to restore the key from. - * @return The restored Key object. - */ - @Deprecated( - "This method has been deprecated and should no longer be used.", - ReplaceWith("restorePrivateKey(restoreIdentifier, privateKeyData)"), - DeprecationLevel.ERROR - ) - fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey - /** * Restores a private key using a restoration identifier and private key data encoded in base 64. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoImpl.kt index b20485109..3b8980e0d 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pluto/PlutoImpl.kt @@ -382,28 +382,6 @@ class PlutoImpl( .insert(LinkSecretDB(linkSecret)) } - /** - * Stores the metadata associated with a credential request. - * - * @param name the unique name used to retrieve the stored metadata. - * @param metadata The metadata to store. It must be an instance of [CredentialRequestMeta]. - * - * @deprecated This method has been deprecated and should no longer be used. - * @see storeCredentialMetadata(name, linkSecretName, json) for the replacement method that should be used. - */ - @Deprecated( - "This method has been deprecated and should no longer be used.", - ReplaceWith("storeCredentialMetadata(name, linkSecretName, json)"), - DeprecationLevel.ERROR - ) - override fun storeCredentialMetadata(name: String, metadata: CredentialRequestMeta) { - getInstance().credentialMetadataQueries.insert( - id = name, - linkSecretName = metadata.linkSecretName, - json = metadata.json - ) - } - /** * Stores the metadata associated with a credential request. * From 490e9703b9ae8c4184f568e6b1b1c9be47057471 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 14:46:12 -0400 Subject: [PATCH 10/22] fix: linting issues Signed-off-by: Cristian G --- .../identus/walletsdk/apollo/ApolloImpl.kt | 1 - .../walletsdk/edgeagent/EdgeAgentError.kt | 2 -- .../walletsdk/pollux/DescriptorPath.kt | 2 +- .../identus/walletsdk/pollux/PolluxImpl.kt | 3 +-- .../pollux/models/SDJWTCredential.kt | 21 ------------------- 5 files changed, 2 insertions(+), 27 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt index 5fe04dea8..89d6ea35b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt @@ -38,7 +38,6 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.LogLevel diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt index 6b3f71ff6..f2c7065da 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentError.kt @@ -4,8 +4,6 @@ import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.Error import org.hyperledger.identus.walletsdk.domain.models.KnownPrismError -import kotlin.reflect.KClass -import kotlin.reflect.jvm.internal.impl.load.java.structure.JavaClass sealed class EdgeAgentError : KnownPrismError() { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt index 3064b62b6..f079b4a44 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/DescriptorPath.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.json.longOrNull class DescriptorPath(private val jsonElement: JsonElement) { - fun getValue (path: String): Any? { + fun getValue(path: String): Any? { val regex = Regex("[/\\[\\]`.]+") val segments = path.split(regex).filter { it.isNotEmpty() } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 4b98b69f1..8d54685fc 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -104,9 +104,9 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsSDJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions +import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl -import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.pluto.RestorationID import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefinitionRequest @@ -1247,7 +1247,6 @@ open class PolluxImpl( val inputDescriptor = inputDescriptors.find { it.id == descriptorItem.id } verifyInputDescriptors(inputDescriptor, Json.encodeToJsonElement(verifiableCredential)) - } ?: throw PolluxError.VerificationUnsuccessful("Invalid submission, no value found for $pathNested") return true } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt index 3d1ae2352..c779a2e81 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/models/SDJWTCredential.kt @@ -1,30 +1,13 @@ package org.hyperledger.identus.walletsdk.pollux.models -import com.nimbusds.jose.JOSEObjectType -import com.nimbusds.jose.JWSAlgorithm -import com.nimbusds.jose.crypto.ECDSASigner -import com.nimbusds.jwt.SignedJWT -import eu.europa.ec.eudi.sdjwt.ClaimValidations.primitiveClaim import eu.europa.ec.eudi.sdjwt.JsonPointer import eu.europa.ec.eudi.sdjwt.JwtAndClaims import eu.europa.ec.eudi.sdjwt.JwtSignatureVerifier import eu.europa.ec.eudi.sdjwt.NoSignatureValidation import eu.europa.ec.eudi.sdjwt.SdJwt -import eu.europa.ec.eudi.sdjwt.SdJwtFactory -import eu.europa.ec.eudi.sdjwt.SdJwtIssuer import eu.europa.ec.eudi.sdjwt.SdJwtVerifier -import eu.europa.ec.eudi.sdjwt.exp -import eu.europa.ec.eudi.sdjwt.iat -import eu.europa.ec.eudi.sdjwt.iss -import eu.europa.ec.eudi.sdjwt.name -import eu.europa.ec.eudi.sdjwt.nimbus -import eu.europa.ec.eudi.sdjwt.plain import eu.europa.ec.eudi.sdjwt.present -import eu.europa.ec.eudi.sdjwt.recreateClaims -import eu.europa.ec.eudi.sdjwt.sd -import eu.europa.ec.eudi.sdjwt.sdJwt import eu.europa.ec.eudi.sdjwt.serialize -import eu.europa.ec.eudi.sdjwt.sub import kotlinx.coroutines.runBlocking import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Transient @@ -44,11 +27,7 @@ import org.hyperledger.identus.walletsdk.domain.models.CredentialOperationsOptio import org.hyperledger.identus.walletsdk.domain.models.PolluxError import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential import org.hyperledger.identus.walletsdk.domain.models.StorableCredential -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey -import java.security.interfaces.ECPrivateKey import java.util.* -import kotlinx.serialization.json.put @OptIn(ExperimentalSerializationApi::class) data class SDJWTCredential( From e052705f2fcd24a626a187f87a6659267f9d9b28 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 14:53:11 -0400 Subject: [PATCH 11/22] fix: linting issues Signed-off-by: Cristian G --- .../walletsdk/edgeagent/EdgeAgentTests.kt | 107 +++++++++--------- .../walletsdk/pollux/PolluxImplTest.kt | 2 - 2 files changed, 53 insertions(+), 56 deletions(-) diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index ad951000a..818aa978c 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -90,7 +90,6 @@ import org.hyperledger.identus.walletsdk.pollux.PolluxImpl import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.CredentialRequestMeta import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential -import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.PresentationSubmission import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefinitionRequest @@ -2141,63 +2140,63 @@ class EdgeAgentTests { assertTrue(jwt.contains(".")) } - @Test - fun `test connectionless credential offer correctly`() = runTest { - val agent = spy( - EdgeAgent( - apollo = apolloMock, - castor = castorMock, - pluto = plutoMock, - mercury = mercuryMock, - pollux = polluxMock, - connectionManager = connectionManagerMock, - seed = seed, - api = null, - logger = LoggerMock() - ) + @Test + fun `test connectionless credential offer correctly`() = runTest { + val agent = spy( + EdgeAgent( + apollo = apolloMock, + castor = castorMock, + pluto = plutoMock, + mercury = mercuryMock, + pollux = polluxMock, + connectionManager = connectionManagerMock, + seed = seed, + api = null, + logger = LoggerMock() ) + ) - val notExpiredTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(30) - val notExpiredInvitation = - """{"id":"f96e3699-591c-4ae7-b5e6-6efe6d26255b","type":"https://didcomm.org/out-of-band/2.0/invitation","from":"did:peer:2.Ez6LSfsKMe8vSSWkYdZCpn4YViPERfdGAhdLAGHgx2LGJwfmA.Vz6Mkpw1kSabBMzkA3v59tQFnh3FtkKy6xLhLxd9S6BAoaBg2.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuMzc6ODA4MC9kaWRjb21tIiwiciI6W10sImEiOlsiZGlkY29tbS92MiJdfX0","body":{"goal_code":"issue-vc","goal":"To issue a Faber College Graduate credential","accept":["didcomm/v2"]},"attachments":[{"id":"70cdc90c-9a99-4cda-87fe-4f4b2595112a","media_type":"application/json","data":{"json":{"id":"655e9a2c-48ed-459b-b3da-6b3686655564","type":"https://didcomm.org/issue-credential/3.0/offer-credential","body":{"goal_code":"Offer Credential","credential_preview":{"type":"https://didcomm.org/issue-credential/3.0/credential-credential","body":{"attributes":[{"name":"familyName","value":"Wonderland"},{"name":"givenName","value":"Alice"},{"name":"drivingClass","value":"Mw==","media_type":"application/json"},{"name":"dateOfIssuance","value":"2020-11-13T20:20:39+00:00"},{"name":"emailAddress","value":"alice@wonderland.com"},{"name":"drivingLicenseID","value":"12345"}]}}},"attachments":[{"id":"8404678b-9a36-4989-af1d-0f445347e0e3","media_type":"application/json","data":{"json":{"options":{"challenge":"ad0f43ad-8538-41d4-9cb8-20967bc685bc","domain":"domain"},"presentation_definition":{"id":"748efa58-2bce-440d-921f-2520a8446663","input_descriptors":[],"format":{"jwt":{"alg":["ES256K"],"proof_type":[]}}}}},"format":"prism/jwt"}],"thid":"f96e3699-591c-4ae7-b5e6-6efe6d26255b","from":"did:peer:2.Ez6LSfsKMe8vSSWkYdZCpn4YViPERfdGAhdLAGHgx2LGJwfmA.Vz6Mkpw1kSabBMzkA3v59tQFnh3FtkKy6xLhLxd9S6BAoaBg2.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuMzc6ODA4MC9kaWRjb21tIiwiciI6W10sImEiOlsiZGlkY29tbS92MiJdfX0"}}}],"created_time":1724851139,"expires_time":$notExpiredTime}""" - val base64Invitation = notExpiredInvitation.base64UrlEncoded - doReturn(DID("did:peer:asdf")).`when`(agent).createNewPeerDID(updateMediator = true) - - val outOfBandUrl = "https://my.domain.com/path?_oob=$base64Invitation" - val connectionlessCredentialOffer = agent.parseInvitation(outOfBandUrl) - assertTrue(connectionlessCredentialOffer is ConnectionlessCredentialOffer) - connectionlessCredentialOffer as ConnectionlessCredentialOffer - val msg = connectionlessCredentialOffer.offerCredential.makeMessage() - - assertEquals(ProtocolType.DidcommOfferCredential.value, msg.piuri) - assertEquals("f96e3699-591c-4ae7-b5e6-6efe6d26255b", msg.thid) - val attachments = msg.attachments - assertEquals(1, attachments.size) - val attachmentJsonData = attachments.first().data - assertTrue(attachmentJsonData is AttachmentData.AttachmentJsonData) - val json = Json.parseToJsonElement(attachmentJsonData.getDataAsJsonString()) - assertTrue(json.jsonObject.containsKey("options")) - assertTrue(json.jsonObject["options"]!!.jsonObject.containsKey("challenge")) - assertTrue(json.jsonObject["options"]!!.jsonObject.containsKey("domain")) - assertTrue(json.jsonObject.containsKey("presentation_definition")) - assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("id")) - assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("input_descriptors")) - assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("format")) - assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject.contains("jwt")) - assertTrue( - json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject.contains( - "alg" - ) + val notExpiredTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(30) + val notExpiredInvitation = + """{"id":"f96e3699-591c-4ae7-b5e6-6efe6d26255b","type":"https://didcomm.org/out-of-band/2.0/invitation","from":"did:peer:2.Ez6LSfsKMe8vSSWkYdZCpn4YViPERfdGAhdLAGHgx2LGJwfmA.Vz6Mkpw1kSabBMzkA3v59tQFnh3FtkKy6xLhLxd9S6BAoaBg2.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuMzc6ODA4MC9kaWRjb21tIiwiciI6W10sImEiOlsiZGlkY29tbS92MiJdfX0","body":{"goal_code":"issue-vc","goal":"To issue a Faber College Graduate credential","accept":["didcomm/v2"]},"attachments":[{"id":"70cdc90c-9a99-4cda-87fe-4f4b2595112a","media_type":"application/json","data":{"json":{"id":"655e9a2c-48ed-459b-b3da-6b3686655564","type":"https://didcomm.org/issue-credential/3.0/offer-credential","body":{"goal_code":"Offer Credential","credential_preview":{"type":"https://didcomm.org/issue-credential/3.0/credential-credential","body":{"attributes":[{"name":"familyName","value":"Wonderland"},{"name":"givenName","value":"Alice"},{"name":"drivingClass","value":"Mw==","media_type":"application/json"},{"name":"dateOfIssuance","value":"2020-11-13T20:20:39+00:00"},{"name":"emailAddress","value":"alice@wonderland.com"},{"name":"drivingLicenseID","value":"12345"}]}}},"attachments":[{"id":"8404678b-9a36-4989-af1d-0f445347e0e3","media_type":"application/json","data":{"json":{"options":{"challenge":"ad0f43ad-8538-41d4-9cb8-20967bc685bc","domain":"domain"},"presentation_definition":{"id":"748efa58-2bce-440d-921f-2520a8446663","input_descriptors":[],"format":{"jwt":{"alg":["ES256K"],"proof_type":[]}}}}},"format":"prism/jwt"}],"thid":"f96e3699-591c-4ae7-b5e6-6efe6d26255b","from":"did:peer:2.Ez6LSfsKMe8vSSWkYdZCpn4YViPERfdGAhdLAGHgx2LGJwfmA.Vz6Mkpw1kSabBMzkA3v59tQFnh3FtkKy6xLhLxd9S6BAoaBg2.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuMzc6ODA4MC9kaWRjb21tIiwiciI6W10sImEiOlsiZGlkY29tbS92MiJdfX0"}}}],"created_time":1724851139,"expires_time":$notExpiredTime}""" + val base64Invitation = notExpiredInvitation.base64UrlEncoded + doReturn(DID("did:peer:asdf")).`when`(agent).createNewPeerDID(updateMediator = true) + + val outOfBandUrl = "https://my.domain.com/path?_oob=$base64Invitation" + val connectionlessCredentialOffer = agent.parseInvitation(outOfBandUrl) + assertTrue(connectionlessCredentialOffer is ConnectionlessCredentialOffer) + connectionlessCredentialOffer as ConnectionlessCredentialOffer + val msg = connectionlessCredentialOffer.offerCredential.makeMessage() + + assertEquals(ProtocolType.DidcommOfferCredential.value, msg.piuri) + assertEquals("f96e3699-591c-4ae7-b5e6-6efe6d26255b", msg.thid) + val attachments = msg.attachments + assertEquals(1, attachments.size) + val attachmentJsonData = attachments.first().data + assertTrue(attachmentJsonData is AttachmentData.AttachmentJsonData) + val json = Json.parseToJsonElement(attachmentJsonData.getDataAsJsonString()) + assertTrue(json.jsonObject.containsKey("options")) + assertTrue(json.jsonObject["options"]!!.jsonObject.containsKey("challenge")) + assertTrue(json.jsonObject["options"]!!.jsonObject.containsKey("domain")) + assertTrue(json.jsonObject.containsKey("presentation_definition")) + assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("id")) + assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("input_descriptors")) + assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject.containsKey("format")) + assertTrue(json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject.contains("jwt")) + assertTrue( + json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject.contains( + "alg" ) - val algs = - json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject["alg"]!!.jsonArray - assertEquals("ES256K", algs.first().jsonPrimitive.content) - assertTrue( - json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject.contains( - "proof_type" - ) + ) + val algs = + json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject["alg"]!!.jsonArray + assertEquals("ES256K", algs.first().jsonPrimitive.content) + assertTrue( + json.jsonObject["presentation_definition"]!!.jsonObject["format"]!!.jsonObject["jwt"]!!.jsonObject.contains( + "proof_type" ) - } + ) + } val getCredentialDefinitionResponse = "{\"schemaId\":\"http://host.docker.internal:8000/prism-agent/schema-registry/schemas/5e0d5a93-4bfd-3111-a956-5d5bc82f76cc\",\"type\":\"CL\",\"tag\":\"licence\",\"value\":{\"primary\":{\"n\":\"105195159277979097653318357586659371305119697478469834190626350283715795188687389523188659352120689851168860621983864738336838773213022505168653440146374011050277159372491059901432822905781969400722059341786498751125483895348734607382548396665339315322605154516776326303787844694026898270194867398625429469096229269732265502538641116512214652017416624138065704599041020588805936844771273861390913500753293895219370960892829297672575154196820931047049021760519166121287056337193413235473255257349024671869248216238831094979209384406168241010010012567685965827447177652200129684927663161550376084422586141212281146491949\",\"s\":\"85376740935726732134199731472843597191822272986425414914465211197069650618238336366149699822721009443794877925725075553195071288777117865451699414058058985000654277974066307286552934230286237253977472401290858765904161191229985245519871949378628131263513153683765553672655918133136828182050729012388157183851720391379381006921499997765191873729408614024320763554099291141052786589157823043612948619201525441997065264492145372001259366749278235381762443117203343617927241093647322654346302447381494008414208398219626199373278313446814209403507903682881070548386699522575055488393512785511441688197244526708647113340516\",\"r\":{\"dateofissuance\":\"16159515692057558658031632775257139859912833740243870833808276956469677196577164655991169139545328065546186056342530531355718904597216453319851305621683589202769847381737819412615902541110462703838858425423753481085962114120185123089078513531045426316918036549403698066078445947881055316312848598741184161901260446303171175343050250045452903485086185722998336149005743485268486377824763449026501058416292877646187105446333888525480394665310217044483841168928926515929150167890936706159800372381200383816724043496032886366767166850459338411710056171379538841845247931898550165532492578625954615979453881721709564750235\",\"drivingclass\":\"83649701835078373520097916558245060224505938113940626586910000950978790663411517512280043632278010831292224659523658613504637416710001103641231226266903556936380105758523760424939825687213460920436570466066231912959327201876189240504388424799892400351592593406285436824571943165913587899115814843543998396726679289422080229750418336051741708013580146373647528674381958028243228435161765957312248113519708734663989428761879029086059388435772829434952754093999424834120341657211221855300108096057633128467059590470639772605075954658131680801785637700237403873940041665483384938586320674338994185073499523485570537331062\",\"emailaddress\":\"96995643129591814391344614133120459563648002327749700279517548454036811217735867585059116635583558148259032071807493674533230465312311981127622542797279917256478867847832932893748528200469349058284133058865149153179959849308383505167342565738382180666525211256221655129861213392455759272915565057394420728271409215556596974900718332893753172173500744392522771654048192448229319313386967045678744665093451560743782910263014930200762027209565313884859542996067229707388839912195826334964819133016500346618083969320902775088800287566711941842968839787149808739739233388585677095545116231323172342995837636586249573194609\",\"drivinglicenseid\":\"102840929811153624977554462471309185033977661854754815794111114507549576719389525167082631547450413573293352276930065480432301200611396989595571202142654033217842162456070556560693402484110499573693863745648118310258284468114751958738878996458420605301017450868522680454545537837403398645500541915771765220093329728663621098538954397330411649083351383375839056527007892276284168437065687748085384178113959961057476582871100422859953560730152958588610850909069434658487744782540788968302663076149478487413357533660817020800754493642858564081116318655661240523146995256712471572605700346459123074377380656921337264554594\",\"familyname\":\"2428690037146701497427424649573806616639612325136606164619283916796880313617677563507218774958436668407050506838114136163250163675016510113975582318007560622124292458766639319715064358235569650961433812439763343736699708535945693241909905707497180931492818502593885932421170612418693515054756633264933222189766691632082890045477718331705366111669009551578289182848340651375008362238266590844461708981816856194045325523248527964502118319210042254240848590574645476930113881493472578612352948284862674703949781070309344526122291448990325949065193279599181502524961004046979227803224474342778516917124487012958845744311\",\"master_secret\":\"96236339155824229583363924057798366491998077727991424922911165403434522806469328114407334094535810942859512352089785125683335350062474092708044674085769524387654467267128528564551803293661877480971961092735622606052503557881856409855812611523475975566606131897917979412576797874632169829901968854843162299366867885636535326810998541141840561418097240137120398317445832694001031827068485975315937269024666370665530455146256019590700349556357390218401217383173228376078058967743472704019765210324846681867991543267171763037513180046865961560351035005185946817643006206395175857900512245900162751815626427008481585714891\"},\"rctxt\":\"54359809198312125478916383106913469635175253891208897419510030559787479974126666313900084654632259260010008369569778456071591398552341004538623276997178295939490854663263886825856426285604332554317424030793691008221895556474599466123873279022389276698551452690414982831059651505731449763128921782866843113361548859434294057249048041670761184683271568216202174527891374770703485794299697663353847310928998125365841476766767508733046891626759537001358973715760759776149482147060701775948253839125589216812475133616408444838011643485797584321993661048373877626880635937563283836661934456534313802815974883441215836680800\",\"z\":\"99592262675748359673042256590146366586480829950402370244401571195191609039150608482506917768910598228167758026656953725016982562881531475875469671976107506976812319765644401707559997823702387678953647104105378063905395973550729717937712350758544336716556268064226491839700352305793370980462034813589488455836259737325502578253339820590260554457468082536249525493340350556649403477875367398139579018197084796440810685458274393317299082017275568964540311198115802021902455672385575542594821996060452628805634468222196284384514736044680778624637228114693554834388824212714580770066729185685978935409859595244639193538156\"}},\"issuerId\":\"did:prism:604ba1764ab89993f9a74625cc4f3e04737919639293eb382cc7adc53767f550\"}" diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 56b13b22d..4de036b2c 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -26,7 +26,6 @@ import org.didcommx.didcomm.common.Typ import org.hyperledger.identus.apollo.base64.base64UrlDecoded import org.hyperledger.identus.apollo.derivation.MnemonicHelper import org.hyperledger.identus.walletsdk.apollo.ApolloImpl -import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1KeyPair import org.hyperledger.identus.walletsdk.castor.CastorImpl import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo @@ -59,7 +58,6 @@ import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.PresentationSubmissionOptionsSDJWT import org.hyperledger.identus.walletsdk.edgeagent.protocols.proofOfPresentation.SDJWTPresentationOptions import org.hyperledger.identus.walletsdk.logger.Logger -import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.AnoncredsPresentationDefinitionRequest import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTPresentationDefinitionRequest From 5355e93cc47567e6733e4723db0c678376830dee Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 14:59:45 -0400 Subject: [PATCH 12/22] fix: linting issues Signed-off-by: Cristian G --- .../walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt | 2 +- .../walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt | 1 - .../walletsdk/sampleapp/ui/messages/MessagesViewModel.kt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt index 7819d44fe..48dbfce1d 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsAdapter.kt @@ -10,13 +10,13 @@ import androidx.recyclerview.widget.RecyclerView import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.pollux.models.AnonCredential import org.hyperledger.identus.walletsdk.pollux.models.JWTCredential +import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential import org.hyperledger.identus.walletsdk.pollux.models.W3CCredential import org.hyperledger.identus.walletsdk.sampleapp.R import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter -import org.hyperledger.identus.walletsdk.pollux.models.SDJWTCredential class CredentialsAdapter( private var data: MutableList = mutableListOf(), diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt index 5d105991c..8aa717fa3 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/credentials/CredentialsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import androidx.lifecycle.viewmodel.viewModelFactory import kotlinx.coroutines.launch import org.hyperledger.identus.walletsdk.domain.models.Credential import org.hyperledger.identus.walletsdk.sampleapp.Sdk diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index b0c821953..1bbf4671b 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -20,7 +20,6 @@ import org.hyperledger.identus.walletsdk.domain.models.CredentialType import org.hyperledger.identus.walletsdk.domain.models.DID import org.hyperledger.identus.walletsdk.domain.models.DIDDocument import org.hyperledger.identus.walletsdk.domain.models.InputFieldFilter -import org.hyperledger.identus.walletsdk.domain.models.JWTPresentationClaims import org.hyperledger.identus.walletsdk.domain.models.Message import org.hyperledger.identus.walletsdk.domain.models.ProvableCredential import org.hyperledger.identus.walletsdk.domain.models.SDJWTPresentationClaims From e729ac6497f95061368f91637486043d1f869795 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 15:17:30 -0400 Subject: [PATCH 13/22] fix: linting issues Signed-off-by: Cristian G --- .../org/hyperledger/identus/walletsdk/castor/ApolloMock.kt | 5 ----- .../hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt | 5 ----- .../org/hyperledger/identus/walletsdk/edgeagent/PlutoMock.kt | 4 ---- .../org/hyperledger/identus/walletsdk/mercury/PlutoMock.kt | 4 ---- 4 files changed, 18 deletions(-) diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/ApolloMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/ApolloMock.kt index bec4c62b9..489bb1af8 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/ApolloMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/ApolloMock.kt @@ -14,7 +14,6 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyPair import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey class ApolloMock : Apollo { var createRandomMnemonicsReturn: Array = emptyArray() @@ -62,10 +61,6 @@ class ApolloMock : Apollo { TODO("Not yet implemented") } - override fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey { - TODO("Not yet implemented") - } - override fun restorePrivateKey(restorationIdentifier: String, privateKeyData: String): PrivateKey { TODO("Not yet implemented") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt index 7ef245ba9..eb0bf336b 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt @@ -13,7 +13,6 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyPair import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey class ApolloMock : Apollo { var createRandomMnemonicsReturn: Array = emptyArray() @@ -55,10 +54,6 @@ class ApolloMock : Apollo { TODO("Not yet implemented") } - override fun restorePrivateKey(storablePrivateKey: StorablePrivateKey): PrivateKey { - TODO("Not yet implemented") - } - override fun restorePrivateKey(restorationIdentifier: String, privateKeyData: String): PrivateKey { TODO("Not yet implemented") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/PlutoMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/PlutoMock.kt index 384c83761..276468fb2 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/PlutoMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/PlutoMock.kt @@ -246,10 +246,6 @@ class PlutoMock : Pluto { override fun storeLinkSecret(linkSecret: String) { } - override fun storeCredentialMetadata(name: String, metadata: CredentialRequestMeta) { - TODO("Not yet implemented") - } - override fun storeCredentialMetadata(name: String, linkSecretName: String, json: String) { TODO("Not yet implemented") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/PlutoMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/PlutoMock.kt index 0b0223071..51f89fb4c 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/PlutoMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/PlutoMock.kt @@ -195,10 +195,6 @@ class PlutoMock : Pluto { TODO("Not yet implemented") } - override fun storeCredentialMetadata(name: String, metadata: CredentialRequestMeta) { - TODO("Not yet implemented") - } - override fun storeCredentialMetadata(name: String, linkSecretName: String, json: String) { TODO("Not yet implemented") } From 7165cb26d9fbee98749bb7a0f18de4ffe9280981 Mon Sep 17 00:00:00 2001 From: Cristian G Date: Wed, 25 Sep 2024 15:38:12 -0400 Subject: [PATCH 14/22] fix: linting issues Signed-off-by: Cristian G --- .../hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt | 2 +- .../org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index 818aa978c..55a54a7cc 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -2121,7 +2121,7 @@ class EdgeAgentTests { ) val msg = Json.decodeFromString( - """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjI0YTdlNWU4LWQ3YjQtNDUxYy1hOThkLTA3ZDY4NjVhMzQwYSIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjNmMWYzYTliLThjZDUtNDZkMS04Y2E0LTBlYzYyN2YxZTdmMiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiXSwiaWQiOiI5MjM3ZmNhMy1lZjcyLTQyOGEtYjIyYy02YzVmZTZmZTU0NWIiLCJuYW1lIjoiZmFtaWx5TmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZ2l2ZW5OYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5naXZlbk5hbWUiXSwiaWQiOiI3MmQ4NmYwMS04NGYzLTRhNDYtOGFiOC1hN2I0OGE5YjU2MTAiLCJuYW1lIjoiZ2l2ZW5OYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiQWxpY2UifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZHJpdmluZ0NsYXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nQ2xhc3MiXSwiaWQiOiIyNmI3ZmY3Zi1kOTcyLTQxZGYtYTNkZC0zYmE3YzhiNDAwOWEiLCJuYW1lIjoiZHJpdmluZ0NsYXNzIiwiZmlsdGVyIjp7InR5cGUiOiJpbnRlZ2VyIiwicGF0dGVybiI6IjMifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZGF0ZU9mSXNzdWFuY2UiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmRhdGVPZklzc3VhbmNlIl0sImlkIjoiYWZiZmI1NWMtMWY5Ni00ODlkLWJmOGUtYzZhNTUxYjg3ODFjIiwibmFtZSI6ImRhdGVPZklzc3VhbmNlIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiMjAyMC0xMS0xM1QyMDoyMDozOSswMDowMCJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyJdLCJpZCI6IjAyZGIxOTA0LWY3NTMtNDI2NC04ZTA0LWI5NGMxNWFkYzA3MyIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJhbGljZUB3b25kZXJsYW5kLmNvbSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIl0sImlkIjoiODgzYThkMzYtNWYzMy00OGVjLWJhYjktNzE0MGRiYWMyYTVmIiwibmFtZSI6ImRyaXZpbmdMaWNlbnNlSUQiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiIxMjM0NSJ9fV0sImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCJ9fV0sImZvcm1hdCI6eyJzZEp3dCI6eyJhbGciOlsiRVMyNTZrIl19fX0sIm9wdGlvbnMiOnsicHJlc2VudGF0aW9uRnJhbWUiOnt9fX0"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" + """{"id":"56992a63-9871-490a-b9f8-4b1238c23c5e","piuri":"https://didcomm.atalaprism.io/present-proof/3.0/request-presentation","from":{"method":"peer","methodId":"asdf"},"to":{"method":"peer","methodId":"fdsafdsa"},"fromPrior":null,"body":"{\"proof_types\":[]}","created_time":"1726767099","expires_time_plus":"1726853499","attachments":[{"id":"f135525e-26c7-44f5-8f23-b8fbc928bfb2","media_type":"application/json","data":{"base64":"eyJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjI0YTdlNWU4LWQ3YjQtNDUxYy1hOThkLTA3ZDY4NjVhMzQwYSIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjNmMWYzYTliLThjZDUtNDZkMS04Y2E0LTBlYzYyN2YxZTdmMiIsIm5hbWUiOiJQcmVzZW50YXRpb24iLCJwdXJwb3NlIjoiUHJlc2VudGF0aW9uIGRlZmluaXRpb24iLCJmb3JtYXQiOnsic2RKd3QiOnsiYWxnIjpbIkVTMjU2ayJdfX0sImNvbnN0cmFpbnRzIjp7ImZpZWxkcyI6W3sicGF0aCI6WyIkLnZjLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmZhbWlseU5hbWUiXSwiaWQiOiI5MjM3ZmNhMy1lZjcyLTQyOGEtYjIyYy02YzVmZTZmZTU0NWIiLCJuYW1lIjoiZmFtaWx5TmFtZSIsImZpbHRlciI6eyJ0eXBlIjoic3RyaW5nIiwicGF0dGVybiI6IldvbmRlcmxhbmQifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZ2l2ZW5OYW1lIiwiJC5jcmVkZW50aWFsU3ViamVjdC5naXZlbk5hbWUiXSwiaWQiOiI3MmQ4NmYwMS04NGYzLTRhNDYtOGFiOC1hN2I0OGE5YjU2MTAiLCJuYW1lIjoiZ2l2ZW5OYW1lIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiQWxpY2UifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZHJpdmluZ0NsYXNzIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nQ2xhc3MiXSwiaWQiOiIyNmI3ZmY3Zi1kOTcyLTQxZGYtYTNkZC0zYmE3YzhiNDAwOWEiLCJuYW1lIjoiZHJpdmluZ0NsYXNzIiwiZmlsdGVyIjp7InR5cGUiOiJpbnRlZ2VyIiwicGF0dGVybiI6IjMifX0seyJwYXRoIjpbIiQudmMuY3JlZGVudGlhbFN1YmplY3QuZGF0ZU9mSXNzdWFuY2UiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmRhdGVPZklzc3VhbmNlIl0sImlkIjoiYWZiZmI1NWMtMWY5Ni00ODlkLWJmOGUtYzZhNTUxYjg3ODFjIiwibmFtZSI6ImRhdGVPZklzc3VhbmNlIiwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJwYXR0ZXJuIjoiMjAyMC0xMS0xM1QyMDoyMDozOSswMDowMCJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5lbWFpbEFkZHJlc3MiLCIkLmNyZWRlbnRpYWxTdWJqZWN0LmVtYWlsQWRkcmVzcyJdLCJpZCI6IjAyZGIxOTA0LWY3NTMtNDI2NC04ZTA0LWI5NGMxNWFkYzA3MyIsIm5hbWUiOiJlbWFpbEFkZHJlc3MiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiJhbGljZUB3b25kZXJsYW5kLmNvbSJ9fSx7InBhdGgiOlsiJC52Yy5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIiwiJC5jcmVkZW50aWFsU3ViamVjdC5kcml2aW5nTGljZW5zZUlEIl0sImlkIjoiODgzYThkMzYtNWYzMy00OGVjLWJhYjktNzE0MGRiYWMyYTVmIiwibmFtZSI6ImRyaXZpbmdMaWNlbnNlSUQiLCJmaWx0ZXIiOnsidHlwZSI6InN0cmluZyIsInBhdHRlcm4iOiIxMjM0NSJ9fV0sImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCJ9fV0sImZvcm1hdCI6eyJzZEp3dCI6eyJhbGciOlsiRVMyNTZrIl19fX19"},"format":"dif/presentation-exchange/definitions@v1.0"}],"thid":"11001ef6-f4c9-430c-84d7-ef74f0689e9f","ack":[],"direction":"SENT"}""" ) val presentation = agent.preparePresentationForRequestProof( diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 4de036b2c..804273b40 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -201,7 +201,7 @@ class PolluxImplTest { definitionRequest.presentationDefinition.inputDescriptors.first().constraints.fields?.size ) assertEquals( - 2, + 3, definitionRequest.presentationDefinition.inputDescriptors.first().constraints.fields?.first()?.path?.size ) assertEquals( From 0e1feafb131f96b1874f41cfd043c15d5cf863ff Mon Sep 17 00:00:00 2001 From: Cristian G Date: Thu, 26 Sep 2024 15:23:36 -0400 Subject: [PATCH 15/22] feat: create prism did with Ed25519 Signed-off-by: Cristian G --- .../identus/walletsdk/apollo/ApolloImpl.kt | 1 + .../castor/did/prismdid/PrismDIDPublicKey.kt | 75 ++++++++++++++++--- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 24 ++++-- .../identus/walletsdk/pollux/PolluxImpl.kt | 2 +- .../castor/PrismDIDPublicKeyTests.kt | 11 ++- .../walletsdk/edgeagent/EdgeAgentTests.kt | 20 +++++ .../sampleapp/ui/dids/DIDsFragment.kt | 3 + .../sampleapp/ui/dids/DIDsViewModel.kt | 7 ++ .../ui/messages/MessagesViewModel.kt | 21 +----- .../src/main/res/layout/fragment_dids.xml | 11 +++ 10 files changed, 136 insertions(+), 39 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt index 89d6ea35b..5fe04dea8 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt @@ -38,6 +38,7 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.LogLevel diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt index 14a4f0eca..263430bbf 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt @@ -1,13 +1,20 @@ package org.hyperledger.identus.walletsdk.castor.did.prismdid -import org.hyperledger.identus.apollo.secp256k1.Secp256k1Lib import org.hyperledger.identus.protos.CompressedECKeyData +import org.hyperledger.identus.protos.ECKeyData import org.hyperledger.identus.protos.KeyUsage +import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PublicKey import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1PublicKey import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo import org.hyperledger.identus.walletsdk.domain.models.CastorError import org.hyperledger.identus.walletsdk.domain.models.Curve +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurveKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurvePointXKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurvePointYKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyTypes import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import pbandk.ByteArr import kotlin.jvm.Throws @@ -54,7 +61,26 @@ class PrismDIDPublicKey { this.usage = proto.usage.fromProto() this.keyData = when (proto.keyData) { is org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData -> { - Secp256k1PublicKey(proto.keyData.value.data.array) + val compressedEcKeyData = proto.compressedEcKeyData!! + apollo.createPublicKey( + properties = mapOf( + TypeKey().property to KeyTypes.EC, + CurveKey().property to compressedEcKeyData.curve, + RawKey().property to compressedEcKeyData.data.array + ) + ) + } + + is org.hyperledger.identus.protos.PublicKey.KeyData.EcKeyData -> { + val ecKeyData = proto.ecKeyData!! + apollo.createPublicKey( + properties = mapOf( + TypeKey().property to KeyTypes.EC, + CurveKey().property to ecKeyData.curve, + CurvePointXKey().property to ecKeyData.x, + CurvePointYKey().property to ecKeyData.y + ) + ) } else -> { @@ -69,14 +95,33 @@ class PrismDIDPublicKey { * @return the converted Protobuf PublicKey object */ fun toProto(): org.hyperledger.identus.protos.PublicKey { - val compressedPublicKey = Secp256k1PublicKey(Secp256k1Lib().compressPublicKey(keyData.getValue())) - return org.hyperledger.identus.protos.PublicKey( - id = id, - usage = usage.toProto(), - keyData = org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData( - compressedPublicKey.toProto() + if (keyData.getCurve() == Curve.SECP256K1.value) { + val x = keyData.getProperty(CurvePointXKey().property) + val y = keyData.getProperty(CurvePointYKey().property) + + return org.hyperledger.identus.protos.PublicKey( + id = id, + usage = usage.toProto(), + keyData = org.hyperledger.identus.protos.PublicKey.KeyData.EcKeyData( + ecKeyData = ECKeyData( + curve = keyData.getCurve(), + x = ByteArr(x.encodeToByteArray()), + y = ByteArr(y.encodeToByteArray()) + ) + ) + ) + } else { + return org.hyperledger.identus.protos.PublicKey( + id = id, + usage = usage.toProto(), + keyData = org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData( + compressedEcKeyData = CompressedECKeyData( + curve = Curve.ED25519.value, + data = ByteArr(keyData.getEncoded()) + ) + ) ) - ) + } } /** @@ -128,6 +173,18 @@ fun Secp256k1PublicKey.toProto(): CompressedECKeyData { ) } +/** + * Converts a Ed25519PublicKey object to a CompressedECKeyData object. + * + * @return the converted CompressedECKeyData object. + */ +fun Ed25519PublicKey.toProto(): CompressedECKeyData { + return CompressedECKeyData( + curve = Curve.ED25519.value, + data = ByteArr(raw) + ) +} + /** * Generates the identifier for a PrismDIDPublicKey.Usage based on the given index. * diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 9357cd43d..b3b553c8f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -374,10 +374,15 @@ open class EdgeAgent { suspend fun createNewPrismDID( keyPathIndex: Int? = null, alias: String? = null, - services: Array = emptyArray() + services: Array = emptyArray(), + format: String? = null ): DID { val index = keyPathIndex ?: (pluto.getPrismLastKeyPathIndex().first() + 1) - val keyPair = Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, index)) + val keyPair = if (format == "vc+sd-jwt") { + Ed25519KeyPair.generateKeyPair() + } else { + Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, index)) + } val did = castor.createPrismDID(masterPublicKey = keyPair.publicKey, services = services) registerPrismDID(did, index, alias, keyPair.privateKey) return did @@ -659,16 +664,21 @@ open class EdgeAgent { CredentialType.JWT, CredentialType.SDJWT -> { val privateKeyKeyPath = pluto.getPrismDIDKeyPathIndex(did).first() - val keyPair = Secp256k1KeyPair.generateKeyPair( - seed, - KeyCurve(Curve.SECP256K1, privateKeyKeyPath) - ) val offerDataString = offer.attachments.firstNotNullOf { it.data.getDataAsJsonString() } val offerJsonObject = Json.parseToJsonElement(offerDataString).jsonObject - val jwtString = + + val jwtString = if (type == CredentialType.JWT) { + val keyPair = Secp256k1KeyPair.generateKeyPair( + seed, + KeyCurve(Curve.SECP256K1, privateKeyKeyPath) + ) pollux.processCredentialRequestJWT(did, keyPair.privateKey, offerJsonObject) + } else { + val keyPair = Ed25519KeyPair.generateKeyPair() + pollux.processCredentialRequestSDJWT(did, keyPair.privateKey, offerJsonObject) + } val attachmentDescriptor = AttachmentDescriptor( mediaType = ContentType.Application.Json.toString(), diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 8d54685fc..a3fbf3943 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -296,7 +296,7 @@ open class PolluxImpl( ): String { val domain = getDomain(offerJson) ?: throw PolluxError.NoDomainOrChallengeFound() val challenge = getChallenge(offerJson) ?: throw PolluxError.NoDomainOrChallengeFound() - return signClaimsRequestCredentialJWT(subjectDID, privateKey, domain, challenge) + return signClaims(subjectDID, privateKey, domain, challenge) } /** diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt index 22575cac4..102255734 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt @@ -3,31 +3,34 @@ package org.hyperledger.identus.walletsdk.castor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.hyperledger.identus.protos.PublicKey +import org.hyperledger.identus.walletsdk.apollo.ApolloImpl import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PublicKey import org.hyperledger.identus.walletsdk.castor.did.prismdid.PrismDIDPublicKey import org.hyperledger.identus.walletsdk.castor.did.prismdid.id +import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo import org.junit.Ignore import org.junit.Test +import org.mockito.kotlin.mock import kotlin.test.assertContentEquals import kotlin.test.assertEquals class PrismDIDPublicKeyTests { @OptIn(ExperimentalCoroutinesApi::class) - @Ignore("PrismDIDPublicKey requires Secp256k1Lib to be an interface in order to mock its result. Once that is done this test can be added back.") +// @Ignore("PrismDIDPublicKey requires Secp256k1Lib to be an interface in order to mock its result. Once that is done this test can be added back.") @Test fun it_should_parse_proto_toPrismDIDPublicKey() = runTest { - val apollo = ApolloMock() - val seed = apollo.createRandomSeed(passphrase = "mnemonics").seed + val apollo = ApolloImpl() +// val seed = apollo.createRandomSeed(passphrase = "mnemonics").seed val keyPair = Ed25519KeyPair( privateKey = Ed25519PrivateKey(ByteArray(0)), publicKey = Ed25519PublicKey(ByteArray(0)) ) val publicKey = PrismDIDPublicKey( - apollo = ApolloMock(), + apollo = apollo, id = PrismDIDPublicKey.Usage.MASTER_KEY.id(0), usage = PrismDIDPublicKey.Usage.MASTER_KEY, keyData = keyPair.publicKey diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index 55a54a7cc..deca17fd8 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -375,6 +375,26 @@ class EdgeAgentTests { assertTrue { plutoMockOld.wasStorePrismDIDAndPrivateKeysCalled } } + @Test + fun testCreateNewPrismDID_whenVcSdJwt_then() = runTest { + val apollo = ApolloImpl() + val castor = CastorImpl(apollo) + val agent = EdgeAgent( + apollo = apollo, + castor = castor, + pluto = plutoMockOld, + mercury = mercuryMockOld, + pollux = polluxMockOld, + connectionManager = connectionManagerOld, + seed = seed, + api = null, + logger = LoggerMock(), + agentOptions = AgentOptions() + ) + plutoMockOld.getPrismLastKeyPathIndexReturn = flow { emit(0) } + val newDID = agent.createNewPrismDID(format = "vc+sd-jwt") + } + @Test fun testCreateNewPeerDID_shouldCreateNewDID_whenCalled() = runTest { val agent = spy( diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsFragment.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsFragment.kt index ab26c847b..10d160f36 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsFragment.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsFragment.kt @@ -32,6 +32,9 @@ class DIDsFragment : Fragment() { binding.createDid.setOnClickListener { viewModel.createPeerDID() } + binding.createPrismDid.setOnClickListener { + viewModel.createPrismDID() + } } override fun onDestroyView() { diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt index 3fa6a671c..4eda6d535 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt @@ -45,4 +45,11 @@ class DIDsViewModel(application: Application) : AndroidViewModel(application) { ) } } + + fun createPrismDID() { + viewModelScope.launch { + val sdk = Sdk.getInstance() + val did = sdk.agent.createNewPrismDID(format = "vc+sd-jwt") + } + } } diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index 1bbf4671b..f1d2d8df5 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -119,29 +119,13 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application toDID = DID(toDID), presentationClaims = SDJWTPresentationClaims( claims = mapOf( - "familyName" to InputFieldFilter( + "firstName" to InputFieldFilter( type = "string", pattern = "Wonderland" ), - "givenName" to InputFieldFilter( - type = "string", - pattern = "Alice" - ), - "drivingClass" to InputFieldFilter( - type = "integer", - pattern = "3" - ), - "dateOfIssuance" to InputFieldFilter( - type = "string", - pattern = "2020-11-13T20:20:39+00:00" - ), "emailAddress" to InputFieldFilter( type = "string", pattern = "alice@wonderland.com" - ), - "drivingLicenseID" to InputFieldFilter( - type = "string", - pattern = "12345" ) ) ) @@ -261,7 +245,8 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application processedOffers.add(it) viewModelScope.launch { val offer = OfferCredential.fromMessage(message) - val subjectDID = agent.createNewPrismDID() + val format = message.attachments.first().format + val subjectDID = agent.createNewPrismDID(format = format) val request = agent.prepareRequestCredentialWithIssuer( subjectDID, diff --git a/sampleapp/src/main/res/layout/fragment_dids.xml b/sampleapp/src/main/res/layout/fragment_dids.xml index f6b24d899..30968ab6f 100644 --- a/sampleapp/src/main/res/layout/fragment_dids.xml +++ b/sampleapp/src/main/res/layout/fragment_dids.xml @@ -25,4 +25,15 @@ app:tint="@color/white" android:contentDescription="@string/todo"/> + + \ No newline at end of file From 462e44e74d9dfbf676c4d393955bc928789ea47b Mon Sep 17 00:00:00 2001 From: Cristian G Date: Mon, 30 Sep 2024 12:25:11 -0400 Subject: [PATCH 16/22] feat: prism did support for Ed25519 Signed-off-by: Cristian G --- .../identus/walletsdk/apollo/ApolloImpl.kt | 5 +- .../apollo/utils/Ed25519PrivateKey.kt | 27 ++++-- .../identus/walletsdk/castor/CastorImpl.kt | 6 +- .../castor/did/prismdid/PrismDIDPublicKey.kt | 8 +- .../walletsdk/castor/shared/CastorShared.kt | 5 +- .../walletsdk/domain/buildingblocks/Castor.kt | 3 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 20 ++-- .../identus/walletsdk/pollux/PolluxImpl.kt | 91 +++++++++++++++---- .../walletsdk/pollux/PolluxImplTest.kt | 4 +- .../ui/messages/MessagesViewModel.kt | 7 +- 10 files changed, 130 insertions(+), 46 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt index 5fe04dea8..758c4e19d 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt @@ -44,6 +44,7 @@ import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.LogLevel import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl +import pbandk.ByteArr /** * Apollo defines the set of cryptographic operations that are used in the Atala PRISM. @@ -298,8 +299,8 @@ class ApolloImpl( if (curvePointX != null && curvePointY != null) { // Compressed key val nativePublicKey = KMMECSecp256k1PublicKey.secp256k1FromByteCoordinates( - x = (curvePointX as String).base64UrlDecodedBytes, - y = (curvePointY as String).base64UrlDecodedBytes + x = (curvePointX as ByteArr).array, + y = (curvePointY as ByteArr).array ) return Secp256k1PublicKey(nativePublicKey.raw) } else { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt index 574041d76..e45d0de53 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt @@ -1,6 +1,12 @@ package org.hyperledger.identus.walletsdk.apollo.utils -import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import java.security.KeyFactory +import java.security.spec.PKCS8EncodedKeySpec +import org.bouncycastle.asn1.ASN1Encodable +import org.bouncycastle.asn1.DEROctetString +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo +import org.bouncycastle.asn1.x509.AlgorithmIdentifier import org.bouncycastle.jce.provider.BouncyCastleProvider import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.apollo.utils.KMMEdPrivateKey @@ -15,8 +21,7 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import java.security.KeyFactory -import java.security.spec.PKCS8EncodedKeySpec + /** * Represents a private key for the Ed25519 algorithm. @@ -128,9 +133,17 @@ class Ed25519PrivateKey(nativeValue: ByteArray) : PrivateKey(), SignableKey, Sto get() = "ed25519+priv" override fun jca(): java.security.PrivateKey { - val privateKeyParams = Ed25519PrivateKeyParameters(raw, 0) - val pkcs8Encoded = privateKeyParams.encoded - val keyFactory = KeyFactory.getInstance("Ed25519", BouncyCastleProvider()) - return keyFactory.generatePrivate(PKCS8EncodedKeySpec(pkcs8Encoded)) + val algId: AlgorithmIdentifier = AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519) + // Wrap the private key bytes in a DEROctetString + val privateKey: ASN1Encodable = DEROctetString(raw) + // Create the PrivateKeyInfo structure + val privateKeyInfo: PrivateKeyInfo = PrivateKeyInfo(algId, privateKey) + // Get the PKCS#8 encoded bytes + val pkcs8Bytes: ByteArray = privateKeyInfo.getEncoded() + val keySpec = PKCS8EncodedKeySpec(pkcs8Bytes) + // Get a KeyFactory for Ed25519 using Bouncy Castle + val keyFactory = KeyFactory.getInstance("ed25519", BouncyCastleProvider()) + // Generate the PrivateKey object + return keyFactory.generatePrivate(keySpec) } } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt index 67a348b62..4c0648908 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt @@ -69,12 +69,14 @@ constructor( */ override fun createPrismDID( masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKey: PublicKey ): DID { return CastorShared.createPrismDID( apollo = apollo, masterPublicKey = masterPublicKey, - services = services + services = services, + authenticationKey = authenticationKey ) } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt index 263430bbf..5ae2c08e1 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt @@ -1,5 +1,7 @@ package org.hyperledger.identus.walletsdk.castor.did.prismdid +import org.hyperledger.identus.apollo.base64.base64DecodedBytes +import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes import org.hyperledger.identus.protos.CompressedECKeyData import org.hyperledger.identus.protos.ECKeyData import org.hyperledger.identus.protos.KeyUsage @@ -105,8 +107,8 @@ class PrismDIDPublicKey { keyData = org.hyperledger.identus.protos.PublicKey.KeyData.EcKeyData( ecKeyData = ECKeyData( curve = keyData.getCurve(), - x = ByteArr(x.encodeToByteArray()), - y = ByteArr(y.encodeToByteArray()) + x = ByteArr(x.base64UrlDecodedBytes), + y = ByteArr(y.base64UrlDecodedBytes) ) ) ) @@ -117,7 +119,7 @@ class PrismDIDPublicKey { keyData = org.hyperledger.identus.protos.PublicKey.KeyData.CompressedEcKeyData( compressedEcKeyData = CompressedECKeyData( curve = Curve.ED25519.value, - data = ByteArr(keyData.getEncoded()) + data = ByteArr(keyData.raw) ) ) ) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt index bfc976a57..8d0b71ca3 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt @@ -92,7 +92,8 @@ internal class CastorShared { internal fun createPrismDID( apollo: Apollo, masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKey: PublicKey ): DID { val atalaOperation = AtalaOperation( operation = AtalaOperation.Operation.CreateDid( @@ -109,7 +110,7 @@ internal class CastorShared { apollo = apollo, id = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY.defaultId(), usage = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY, - keyData = masterPublicKey + keyData = authenticationKey ).toProto() ), services = services?.map { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt index 511cb82e4..0f5aceec0 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt @@ -34,7 +34,8 @@ interface Castor { */ fun createPrismDID( masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKey: PublicKey ): DID /** diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index b3b553c8f..e327836dd 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -16,7 +16,6 @@ import com.nimbusds.jose.crypto.X25519Decrypter import com.nimbusds.jose.crypto.X25519Encrypter import com.nimbusds.jose.jwk.OctetKeyPair import com.nimbusds.jose.util.Base64URL -import eu.europa.ec.eudi.sdjwt.serialize import eu.europa.ec.eudi.sdjwt.vc.SD_JWT_VC_TYPE import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.http.ContentType @@ -374,17 +373,18 @@ open class EdgeAgent { suspend fun createNewPrismDID( keyPathIndex: Int? = null, alias: String? = null, - services: Array = emptyArray(), - format: String? = null + services: Array = emptyArray() ): DID { val index = keyPathIndex ?: (pluto.getPrismLastKeyPathIndex().first() + 1) - val keyPair = if (format == "vc+sd-jwt") { - Ed25519KeyPair.generateKeyPair() - } else { - Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, index)) - } - val did = castor.createPrismDID(masterPublicKey = keyPair.publicKey, services = services) - registerPrismDID(did, index, alias, keyPair.privateKey) + val masterKeyPair = Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, index)) + + val authenticationKey = Ed25519KeyPair.generateKeyPair() + val did = castor.createPrismDID( + masterPublicKey = masterKeyPair.publicKey, + services = services, + authenticationKey = authenticationKey.publicKey + ) + registerPrismDID(did, index, alias, masterKeyPair.privateKey) return did } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index a3fbf3943..66f729b0a 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -18,6 +18,7 @@ import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jose.crypto.ECDSAVerifier +import com.nimbusds.jose.crypto.Ed25519Signer import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.util.Base64URL import com.nimbusds.jwt.JWTClaimsSet @@ -27,6 +28,7 @@ import eu.europa.ec.eudi.sdjwt.KeyBindingVerifier import eu.europa.ec.eudi.sdjwt.NoSignatureValidation import eu.europa.ec.eudi.sdjwt.SdJwtVerifier import eu.europa.ec.eudi.sdjwt.recreateClaimsAndDisclosuresPerClaim +import eu.europa.ec.eudi.sdjwt.sub import io.iohk.atala.prism.didcomm.didpeer.core.toJsonElement import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod @@ -50,12 +52,15 @@ import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.spec.ECNamedCurveSpec import org.didcommx.didcomm.common.Typ import org.hyperledger.identus.apollo.base64.base64UrlDecoded import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes +import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.apollo.utils.KMMECSecp256k1PublicKey import org.hyperledger.identus.walletsdk.apollo.helpers.gunzip import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1PrivateKey @@ -721,7 +726,8 @@ open class PolluxImpl( if (privateKey !is ExportableKey) { throw PolluxError.PrivateKeyTypeNotSupportedError("The private key should be ${ExportableKey::class.simpleName}") } - val ecPrivateKey = parsePrivateKey(privateKey) + +// parsePrivateKey(privateKey) val presentation: MutableMap> = mutableMapOf( CONTEXT to setOf(CONTEXT_URL), @@ -738,25 +744,78 @@ open class PolluxImpl( .claim(VP, presentation) .build() + println("DID: $subjectDID") val kid = getSigningKid(subjectDID) // Generate a JWS header with the ES256K algorithm - val header = JWSHeader.Builder(JWSAlgorithm.ES256K) - .keyID(kid) - .build() - - // Sign the JWT with the private key - val jwsObject = SignedJWT(header, claims) - val signer = ECDSASigner( - ecPrivateKey as java.security.PrivateKey, - com.nimbusds.jose.jwk.Curve.SECP256K1 - ) - val provider = BouncyCastleProviderSingleton.getInstance() - signer.jcaContext.provider = provider - jwsObject.sign(signer) + if (privateKey is Secp256k1PrivateKey) { + val header = JWSHeader.Builder(JWSAlgorithm.ES256K) + .keyID(kid) + .build() + + // Sign the JWT with the private key + var jwsObject = SignedJWT(header, claims) + + val ecPrivateKey = privateKey.jca() as ECPrivateKey + val signer = ECDSASigner( + ecPrivateKey as java.security.PrivateKey, + com.nimbusds.jose.jwk.Curve.SECP256K1 + ) + val provider = BouncyCastleProviderSingleton.getInstance() + signer.jcaContext.provider = provider + jwsObject.sign(signer) + // Serialize the JWS object to a string + return jwsObject.serialize() + } else { + val header = JWSHeader.Builder(JWSAlgorithm.EdDSA) + .keyID(kid) + .build() + + // Sign the JWT with the private key + var jwsObject = SignedJWT(header, claims) + + val edPrivateKey = privateKey.jca() as EdDSAPrivateKey + val signer = org.bouncycastle.crypto.signers.Ed25519Signer() + + // Convert the EdDSAPrivateKey to a Bouncy Castle private key parameter + val privateKeyParams = Ed25519PrivateKeyParameters(edPrivateKey.encoded, 0) + signer.init(true, privateKeyParams) + + // Sign the data (message) + val messageBytes = jwsObject.signingInput + signer.update(messageBytes, 0, messageBytes.size) + + // Generate the signature + val signature = signer.generateSignature() + + // Now, set the signature on the JWS object + jwsObject = SignedJWT( + header.toBase64URL(), + claims.toPayload().toBase64URL(), + Base64URL(signature.base64UrlEncoded)) + // Serialize the JWS object to a string + val jwt = jwsObject.serialize() + + + + val vc = JWTCredential.fromJwtString(jwt) + + val didDocHolder = castor.resolveDID(vc.issuer) + val authenticationMethodHolder = + didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } + ?: throw PolluxError.VerificationUnsuccessful("Holder core properties must contain Authentication") + val ecPublicKeysHolder = + extractEcPublicKeyFromVerificationMethod(authenticationMethodHolder) + if (!verifyJWTSignatureWithEcPublicKey( + vc.id, + ecPublicKeysHolder + ) + ) { + println("Stop") + } - // Serialize the JWS object to a string - return jwsObject.serialize() + return jwt + } } override suspend fun createPresentationDefinitionRequest( diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 804273b40..609d038ca 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -13,6 +13,7 @@ import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode import io.ktor.serialization.kotlinx.json.json +import java.security.interfaces.ECPrivateKey import java.text.SimpleDateFormat import java.util.* import junit.framework.TestCase.assertFalse @@ -1170,7 +1171,8 @@ class PolluxImplTest { }""" ) - val ecPrivateKey = pollux.parsePrivateKey(testCaseOptions.issuerPrv) + val ecPrivateKey = testCaseOptions.issuerPrv.jca() as ECPrivateKey +// pollux.parsePrivateKey(testCaseOptions.issuerPrv) val claims = JWTClaimsSet.Builder() .issuer(testCaseOptions.issuer.toString()) .audience(testCaseOptions.domain) diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt index f1d2d8df5..c24b7a78e 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/messages/MessagesViewModel.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.hyperledger.identus.walletsdk.castor.CastorImpl import org.hyperledger.identus.walletsdk.db.AppDatabase import org.hyperledger.identus.walletsdk.db.DatabaseClient import org.hyperledger.identus.walletsdk.domain.DIDCOMM_MESSAGING @@ -245,8 +246,10 @@ class MessagesViewModel(application: Application) : AndroidViewModel(application processedOffers.add(it) viewModelScope.launch { val offer = OfferCredential.fromMessage(message) - val format = message.attachments.first().format - val subjectDID = agent.createNewPrismDID(format = format) + val subjectDID = agent.createNewPrismDID() + + val x = (agent.castor as CastorImpl).resolvers.first().resolve(subjectDID.toString()) + val didDoc = agent.castor.resolveDID(subjectDID.toString()) val request = agent.prepareRequestCredentialWithIssuer( subjectDID, From 553247c94b0c9c4f63995a9e8b60ea87f0b176f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Tue, 5 Nov 2024 11:40:15 +0100 Subject: [PATCH 17/22] fix: Edge Agent should not be calling Apollo classes directly but Apollo instead fix: code improvements to allow passing additional authentication keys to Castor.createPrismDID --- .../identus/walletsdk/castor/CastorImpl.kt | 4 +- .../walletsdk/castor/shared/CastorShared.kt | 42 ++++++++++++------- .../walletsdk/domain/buildingblocks/Castor.kt | 2 +- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 42 ++++++++++++------- .../castor/PrismDIDPublicKeyTests.kt | 3 -- .../identus/walletsdk/edgeagent/CastorMock.kt | 3 +- .../walletsdk/edgeagent/EdgeAgentTests.kt | 2 +- .../identus/walletsdk/mercury/CastorMock.kt | 3 +- .../identus/walletsdk/pollux/CastorMock.kt | 3 +- .../walletsdk/pollux/PolluxImplTest.kt | 40 +++++++++++------- .../sampleapp/ui/dids/DIDsViewModel.kt | 2 +- 11 files changed, 89 insertions(+), 57 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt index 4c0648908..989faa6c4 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt @@ -70,13 +70,13 @@ constructor( override fun createPrismDID( masterPublicKey: PublicKey, services: Array?, - authenticationKey: PublicKey + authenticationKeys: Array ): DID { return CastorShared.createPrismDID( apollo = apollo, masterPublicKey = masterPublicKey, services = services, - authenticationKey = authenticationKey + authenticationKeys = authenticationKeys ) } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt index 8d0b71ca3..39d9c1f7b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt @@ -93,26 +93,38 @@ internal class CastorShared { apollo: Apollo, masterPublicKey: PublicKey, services: Array?, - authenticationKey: PublicKey + authenticationKeys: Array ): DID { + val pks: MutableList = mutableListOf() + + pks.add( + PrismDIDPublicKey( + apollo = apollo, + id = PrismDIDPublicKey.Usage.MASTER_KEY.defaultId(), + usage = PrismDIDPublicKey.Usage.MASTER_KEY, + keyData = masterPublicKey + ).toProto() + ) + + // Add a public key for each authentication key + for (authKey in authenticationKeys) { + pks.add( + PrismDIDPublicKey( + apollo = apollo, + id = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY.defaultId(), + usage = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY, + keyData = authKey + ).toProto() + ) + } + + + val atalaOperation = AtalaOperation( operation = AtalaOperation.Operation.CreateDid( CreateDIDOperation( didData = CreateDIDOperation.DIDCreationData( - publicKeys = listOf( - PrismDIDPublicKey( - apollo = apollo, - id = PrismDIDPublicKey.Usage.MASTER_KEY.defaultId(), - usage = PrismDIDPublicKey.Usage.MASTER_KEY, - keyData = masterPublicKey - ).toProto(), - PrismDIDPublicKey( - apollo = apollo, - id = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY.defaultId(), - usage = PrismDIDPublicKey.Usage.AUTHENTICATION_KEY, - keyData = authenticationKey - ).toProto() - ), + publicKeys = pks, services = services?.map { Service( id = it.id, diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt index 0f5aceec0..b1db0ea78 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/domain/buildingblocks/Castor.kt @@ -35,7 +35,7 @@ interface Castor { fun createPrismDID( masterPublicKey: PublicKey, services: Array?, - authenticationKey: PublicKey + authenticationKeys: Array ): DID /** diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index e327836dd..01e39198d 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -82,15 +82,7 @@ import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.Signature import org.hyperledger.identus.walletsdk.domain.models.UnknownError import org.hyperledger.identus.walletsdk.domain.models.httpClient -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurveKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.DerivationPathKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyPair -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyTypes -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.* import org.hyperledger.identus.walletsdk.edgeagent.helpers.AgentOptions import org.hyperledger.identus.walletsdk.edgeagent.mediation.BasicMediatorHandler import org.hyperledger.identus.walletsdk.edgeagent.mediation.MediationHandler @@ -360,7 +352,6 @@ open class EdgeAgent { logger.info(message = "Agent not running") } - // DID Higher Functions /** * This method create a new Prism DID, that can be used to identify the agent and interact with other agents. * @@ -376,15 +367,36 @@ open class EdgeAgent { services: Array = emptyArray() ): DID { val index = keyPathIndex ?: (pluto.getPrismLastKeyPathIndex().first() + 1) - val masterKeyPair = Secp256k1KeyPair.generateKeyPair(seed, KeyCurve(Curve.SECP256K1, index)) - val authenticationKey = Ed25519KeyPair.generateKeyPair() + val secp256k1PrivateKeyProperties: MutableMap = mutableMapOf() + secp256k1PrivateKeyProperties[TypeKey().property] = KeyTypes.EC + secp256k1PrivateKeyProperties[SeedKey().property] = seed.value.base64UrlEncoded + secp256k1PrivateKeyProperties[CurveKey().property] = Curve.SECP256K1.value + secp256k1PrivateKeyProperties[IndexKey().property] = index + + val ed25519PrivateKeyProperties: MutableMap = mutableMapOf() + + ed25519PrivateKeyProperties[TypeKey().property] = KeyTypes.EC + ed25519PrivateKeyProperties[SeedKey().property] = seed.value.base64UrlEncoded + ed25519PrivateKeyProperties[CurveKey().property] = Curve.SECP256K1.value + ed25519PrivateKeyProperties[IndexKey().property] = index + + val masterKey = this.apollo.createPrivateKey( + secp256k1PrivateKeyProperties + ) as Secp256k1PrivateKey + + val authenticationKey = this.apollo.createPrivateKey( + ed25519PrivateKeyProperties + ) as Ed25519PrivateKey + val did = castor.createPrismDID( - masterPublicKey = masterKeyPair.publicKey, + masterPublicKey = masterKey.publicKey(), services = services, - authenticationKey = authenticationKey.publicKey + authenticationKeys = arrayOf( + authenticationKey.publicKey() + ) ) - registerPrismDID(did, index, alias, masterKeyPair.privateKey) + registerPrismDID(did, index, alias, masterKey) return did } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt index 102255734..0242750e8 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/castor/PrismDIDPublicKeyTests.kt @@ -9,10 +9,7 @@ import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PublicKey import org.hyperledger.identus.walletsdk.castor.did.prismdid.PrismDIDPublicKey import org.hyperledger.identus.walletsdk.castor.did.prismdid.id -import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo -import org.junit.Ignore import org.junit.Test -import org.mockito.kotlin.mock import kotlin.test.assertContentEquals import kotlin.test.assertEquals diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/CastorMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/CastorMock.kt index d43c2e879..dfae652e1 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/CastorMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/CastorMock.kt @@ -25,7 +25,8 @@ class CastorMock : Castor { override fun createPrismDID( masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKeys: Array ): DID { return createPrismDIDReturn ?: throw Exception("createPrismDID() not implemented in mock") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index deca17fd8..acd2efeb0 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -392,7 +392,7 @@ class EdgeAgentTests { agentOptions = AgentOptions() ) plutoMockOld.getPrismLastKeyPathIndexReturn = flow { emit(0) } - val newDID = agent.createNewPrismDID(format = "vc+sd-jwt") + val newDID = agent.createNewPrismDID() } @Test diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/CastorMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/CastorMock.kt index bd5e4c773..f6cce338a 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/CastorMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/mercury/CastorMock.kt @@ -27,7 +27,8 @@ class CastorMock : Castor { @Throws(Exception::class) override fun createPrismDID( masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKeys: Array ): DID { return createPrismDIDReturn ?: throw Exception("createPrismDID() not implemented in mock") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/CastorMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/CastorMock.kt index 2e6024c88..762fbe3b4 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/CastorMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/CastorMock.kt @@ -26,7 +26,8 @@ class CastorMock : Castor { override fun createPrismDID( masterPublicKey: PublicKey, - services: Array? + services: Array?, + authenticationKeys: Array ): DID { TODO("Not yet implemented") } diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index 609d038ca..c70b3df3b 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -231,8 +231,16 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID( + issuerKeyPair.publicKey, + emptyArray(), + emptyArray() + ) + val holderDID = castor.createPrismDID( + holderKeyPair.publicKey, + emptyArray(), + emptyArray() + ) pollux = spy(PolluxImpl(apollo, castor, api)) @@ -287,8 +295,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(wrongIssuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(wrongIssuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) val httpResponse = correctHttpResponseFetchRevocationRegistry() @@ -341,8 +349,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) val httpResponse = correctHttpResponseFetchRevocationRegistry() @@ -397,8 +405,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) val httpResponse = correctHttpResponseFetchRevocationRegistry() @@ -450,8 +458,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) pollux = spy(PolluxImpl(apollo, castor, api)) @@ -497,8 +505,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) val httpResponse = correctHttpResponseFetchRevocationRegistry() @@ -550,8 +558,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) val httpResponse = correctHttpResponseFetchRevocationRegistry() @@ -614,8 +622,8 @@ class PolluxImplTest { Seed(MnemonicHelper.createRandomSeed()), KeyCurve(Curve.SECP256K1) ) - val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray()) - val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray()) + val issuerDID = castor.createPrismDID(issuerKeyPair.publicKey, emptyArray(), emptyArray()) + val holderDID = castor.createPrismDID(holderKeyPair.publicKey, emptyArray(), emptyArray()) pollux = spy(PolluxImpl(apollo, castor, api)) diff --git a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt index 4eda6d535..0ad6946cd 100644 --- a/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt +++ b/sampleapp/src/main/java/org/hyperledger/identus/walletsdk/sampleapp/ui/dids/DIDsViewModel.kt @@ -49,7 +49,7 @@ class DIDsViewModel(application: Application) : AndroidViewModel(application) { fun createPrismDID() { viewModelScope.launch { val sdk = Sdk.getInstance() - val did = sdk.agent.createNewPrismDID(format = "vc+sd-jwt") + val did = sdk.agent.createNewPrismDID() } } } From 052a2069763d41d0a86d3c50e170a39edaf285a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Tue, 5 Nov 2024 12:40:34 +0100 Subject: [PATCH 18/22] fix: fix tests --- .../identus/walletsdk/apollo/ApolloImpl.kt | 6 ++---- .../identus/walletsdk/castor/CastorImpl.kt | 3 ++- .../castor/did/prismdid/PrismDIDPublicKey.kt | 6 ++++-- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 4 ++-- .../identus/walletsdk/edgeagent/ApolloMock.kt | 14 +++++++++++++- .../identus/walletsdk/edgeagent/EdgeAgentTests.kt | 2 +- 6 files changed, 24 insertions(+), 11 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt index 758c4e19d..89d6ea35b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/ApolloImpl.kt @@ -38,13 +38,11 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.RawKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import org.hyperledger.identus.walletsdk.logger.LogComponent import org.hyperledger.identus.walletsdk.logger.LogLevel import org.hyperledger.identus.walletsdk.logger.Logger import org.hyperledger.identus.walletsdk.logger.LoggerImpl -import pbandk.ByteArr /** * Apollo defines the set of cryptographic operations that are used in the Atala PRISM. @@ -299,8 +297,8 @@ class ApolloImpl( if (curvePointX != null && curvePointY != null) { // Compressed key val nativePublicKey = KMMECSecp256k1PublicKey.secp256k1FromByteCoordinates( - x = (curvePointX as ByteArr).array, - y = (curvePointY as ByteArr).array + x = (curvePointX as String).base64UrlDecodedBytes, + y = (curvePointY as String).base64UrlDecodedBytes ) return Secp256k1PublicKey(nativePublicKey.raw) } else { diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt index 989faa6c4..172ecfdae 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/CastorImpl.kt @@ -125,7 +125,8 @@ constructor( try { val resolved = resolver.resolve(did) return resolved - } catch (_: CastorError) { + } catch (err: CastorError) { + println(err.message) } } throw Exception("No resolver could resolve the provided DID.") diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt index 5ae2c08e1..bab6e5c41 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt @@ -1,7 +1,9 @@ package org.hyperledger.identus.walletsdk.castor.did.prismdid import org.hyperledger.identus.apollo.base64.base64DecodedBytes +import org.hyperledger.identus.apollo.base64.base64Encoded import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes +import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.protos.CompressedECKeyData import org.hyperledger.identus.protos.ECKeyData import org.hyperledger.identus.protos.KeyUsage @@ -79,8 +81,8 @@ class PrismDIDPublicKey { properties = mapOf( TypeKey().property to KeyTypes.EC, CurveKey().property to ecKeyData.curve, - CurvePointXKey().property to ecKeyData.x, - CurvePointYKey().property to ecKeyData.y + CurvePointXKey().property to ecKeyData.x.array.base64UrlEncoded, + CurvePointYKey().property to ecKeyData.y.array.base64UrlEncoded ) ) } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 01e39198d..1ba089eda 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -378,8 +378,8 @@ open class EdgeAgent { ed25519PrivateKeyProperties[TypeKey().property] = KeyTypes.EC ed25519PrivateKeyProperties[SeedKey().property] = seed.value.base64UrlEncoded - ed25519PrivateKeyProperties[CurveKey().property] = Curve.SECP256K1.value - ed25519PrivateKeyProperties[IndexKey().property] = index + ed25519PrivateKeyProperties[CurveKey().property] = Curve.ED25519.value + ed25519PrivateKeyProperties[IndexKey().property] = index+1 val masterKey = this.apollo.createPrivateKey( secp256k1PrivateKeyProperties diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt index eb0bf336b..118b3a4e8 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt @@ -1,10 +1,14 @@ package org.hyperledger.identus.walletsdk.edgeagent +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecodedBytes +import org.bouncycastle.util.encoders.Hex import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PrivateKey import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519PublicKey import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1PrivateKey import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo +import org.hyperledger.identus.walletsdk.domain.models.Curve import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.SeedWords import org.hyperledger.identus.walletsdk.domain.models.keyManagement.JWK @@ -15,6 +19,8 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey class ApolloMock : Apollo { + val validRawBase64UrlSecp256k1Sk = Hex.decode("67E56582298859DDAE725F972992A07C6C4FB9F62A8FFF58CE3CA926A1063530".lowercase()) + val validRawBase64UrlEd25519Sk = "JLIJQ5jlkyqtGmtOth6yggJLLC0zuRhUPiBhd1-rGPs".base64UrlDecodedBytes var createRandomMnemonicsReturn: Array = emptyArray() var createSeedReturn: Seed = Seed(ByteArray(0)) var createRandomSeedReturn: SeedWords = SeedWords(emptyArray(), Seed(ByteArray(0))) @@ -35,7 +41,13 @@ class ApolloMock : Apollo { } override fun createPrivateKey(properties: Map): PrivateKey { - return createPrivateKey ?: Secp256k1PrivateKey(ByteArray(0)) + val curve = properties["curve"]; + if (curve === Curve.SECP256K1.value) { + return Secp256k1PrivateKey(validRawBase64UrlSecp256k1Sk) + } else if (curve === Curve.ED25519.value) { + return Ed25519PrivateKey(validRawBase64UrlEd25519Sk) + } + TODO("Not yet implemented") } override fun createPublicKey(properties: Map): PublicKey { diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt index acd2efeb0..fd4629b31 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgentTests.kt @@ -380,7 +380,7 @@ class EdgeAgentTests { val apollo = ApolloImpl() val castor = CastorImpl(apollo) val agent = EdgeAgent( - apollo = apollo, + apollo = apolloMockOld, castor = castor, pluto = plutoMockOld, mercury = mercuryMockOld, From e07339071f42065473656e3849f6d1be2774933b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Tue, 5 Nov 2024 12:53:54 +0100 Subject: [PATCH 19/22] fix: improve pollux impl code --- .../identus/walletsdk/pollux/PolluxImpl.kt | 56 ++++++------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 66f729b0a..d901fb53b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -240,7 +240,7 @@ open class PolluxImpl( ): Credential { val cred: Credential when (restorationIdentifier) { - "sd-jwt+credential" -> { + RestorationID.SDJWT.value -> { cred = SDJWTCredential.fromSDJwtString(credentialData.decodeToString()) } @@ -614,20 +614,6 @@ open class PolluxImpl( throw UnknownError.SomethingWentWrongError("Fetch revocation registry failed: ${result.jsonString}") } - /** - * Parses a PrivateKey into an ECPrivateKey. - * - * @param privateKey The PrivateKey to parse. - * @return The parsed ECPrivateKey. - */ - internal fun parsePrivateKey(privateKey: PrivateKey): ECPrivateKey { - return privateKey.jca() as ECPrivateKey - } - - private fun parsePublicKey(publicKey: PublicKey): ECPublicKey { - return publicKey.jca() as ECPublicKey - } - /** * Returns the domain from the given JsonObject. * @@ -687,14 +673,14 @@ open class PolluxImpl( } /** - * Signs the claims for a proof presentation JSON Web Token (JWT). + * Signs the claims for a proof presentation JSON Web Token (SD+JWT). * - * @param subjectDID The DID of the subject for whom the JWT is being created. - * @param privateKey The private key used to sign the JWT. + * @param subjectDID The DID of the subject for whom the SD+JWT is being created. + * @param privateKey The private key used to sign the SD+JWT. * @param credential The credential to be included in the presentation. - * @param domain The domain of the JWT. - * @param challenge The challenge value for the JWT. - * @return The signed JWT as a string. + * @param domain The domain of the SD+JWT. + * @param challenge The challenge value for the SD+JWT. + * @return The signed SD+JWT as a string. */ internal suspend fun signClaimsProofPresentationSDJWT( subjectDID: DID, @@ -726,9 +712,6 @@ open class PolluxImpl( if (privateKey !is ExportableKey) { throw PolluxError.PrivateKeyTypeNotSupportedError("The private key should be ${ExportableKey::class.simpleName}") } - -// parsePrivateKey(privateKey) - val presentation: MutableMap> = mutableMapOf( CONTEXT to setOf(CONTEXT_URL), TYPE to setOf(VERIFIABLE_PRESENTATION) @@ -744,17 +727,19 @@ open class PolluxImpl( .claim(VP, presentation) .build() - println("DID: $subjectDID") val kid = getSigningKid(subjectDID) + + val algorithm = if (privateKey is Secp256k1PrivateKey) { JWSAlgorithm.ES256K } else { JWSAlgorithm.EdDSA } + val header = JWSHeader.Builder(algorithm) + .keyID(kid) + .build() + // Sign the JWT with the private key + var jwsObject = SignedJWT(header, claims) // Generate a JWS header with the ES256K algorithm if (privateKey is Secp256k1PrivateKey) { - val header = JWSHeader.Builder(JWSAlgorithm.ES256K) - .keyID(kid) - .build() - // Sign the JWT with the private key - var jwsObject = SignedJWT(header, claims) + val ecPrivateKey = privateKey.jca() as ECPrivateKey val signer = ECDSASigner( @@ -767,12 +752,8 @@ open class PolluxImpl( // Serialize the JWS object to a string return jwsObject.serialize() } else { - val header = JWSHeader.Builder(JWSAlgorithm.EdDSA) - .keyID(kid) - .build() - // Sign the JWT with the private key - var jwsObject = SignedJWT(header, claims) + val edPrivateKey = privateKey.jca() as EdDSAPrivateKey val signer = org.bouncycastle.crypto.signers.Ed25519Signer() @@ -795,11 +776,7 @@ open class PolluxImpl( Base64URL(signature.base64UrlEncoded)) // Serialize the JWS object to a string val jwt = jwsObject.serialize() - - - val vc = JWTCredential.fromJwtString(jwt) - val didDocHolder = castor.resolveDID(vc.issuer) val authenticationMethodHolder = didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } @@ -813,7 +790,6 @@ open class PolluxImpl( ) { println("Stop") } - return jwt } } From 0343bc744a37eafac8adcb5d6495a1b1f1c3ee08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Tue, 12 Nov 2024 12:16:46 +0100 Subject: [PATCH 20/22] fix: linting issues --- .../apollo/utils/Ed25519PrivateKey.kt | 5 ++--- .../castor/did/prismdid/PrismDIDPublicKey.kt | 5 +---- .../walletsdk/castor/shared/CastorShared.kt | 4 ---- .../identus/walletsdk/edgeagent/EdgeAgent.kt | 13 ++++++++++-- .../identus/walletsdk/pollux/PolluxImpl.kt | 20 ++++++------------- .../identus/walletsdk/edgeagent/ApolloMock.kt | 3 +-- 6 files changed, 21 insertions(+), 29 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt index e45d0de53..e8a52579b 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/apollo/utils/Ed25519PrivateKey.kt @@ -1,7 +1,5 @@ package org.hyperledger.identus.walletsdk.apollo.utils -import java.security.KeyFactory -import java.security.spec.PKCS8EncodedKeySpec import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.DEROctetString import org.bouncycastle.asn1.edec.EdECObjectIdentifiers @@ -21,7 +19,8 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey - +import java.security.KeyFactory +import java.security.spec.PKCS8EncodedKeySpec /** * Represents a private key for the Ed25519 algorithm. diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt index bab6e5c41..f30a21b27 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/did/prismdid/PrismDIDPublicKey.kt @@ -1,7 +1,5 @@ package org.hyperledger.identus.walletsdk.castor.did.prismdid -import org.hyperledger.identus.apollo.base64.base64DecodedBytes -import org.hyperledger.identus.apollo.base64.base64Encoded import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.protos.CompressedECKeyData @@ -82,11 +80,10 @@ class PrismDIDPublicKey { TypeKey().property to KeyTypes.EC, CurveKey().property to ecKeyData.curve, CurvePointXKey().property to ecKeyData.x.array.base64UrlEncoded, - CurvePointYKey().property to ecKeyData.y.array.base64UrlEncoded + CurvePointYKey().property to ecKeyData.y.array.base64UrlEncoded ) ) } - else -> { throw CastorError.InvalidPublicKeyEncoding("prism", "secp256k1") } diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt index 39d9c1f7b..0ca527ca0 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/castor/shared/CastorShared.kt @@ -117,9 +117,6 @@ internal class CastorShared { ).toProto() ) } - - - val atalaOperation = AtalaOperation( operation = AtalaOperation.Operation.CreateDid( CreateDIDOperation( @@ -136,7 +133,6 @@ internal class CastorShared { ) ) ) - val encodedState = atalaOperation.encodeToByteArray() val stateHash = SHA256().digest(encodedState).toHexString() val base64State = encodedState.base64UrlEncoded diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt index 1ba089eda..bb8444fef 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/edgeagent/EdgeAgent.kt @@ -82,7 +82,16 @@ import org.hyperledger.identus.walletsdk.domain.models.Seed import org.hyperledger.identus.walletsdk.domain.models.Signature import org.hyperledger.identus.walletsdk.domain.models.UnknownError import org.hyperledger.identus.walletsdk.domain.models.httpClient -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.* +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurveKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.DerivationPathKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.IndexKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyPair +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyTypes +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SeedKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorableKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.StorablePrivateKey +import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import org.hyperledger.identus.walletsdk.edgeagent.helpers.AgentOptions import org.hyperledger.identus.walletsdk.edgeagent.mediation.BasicMediatorHandler import org.hyperledger.identus.walletsdk.edgeagent.mediation.MediationHandler @@ -379,7 +388,7 @@ open class EdgeAgent { ed25519PrivateKeyProperties[TypeKey().property] = KeyTypes.EC ed25519PrivateKeyProperties[SeedKey().property] = seed.value.base64UrlEncoded ed25519PrivateKeyProperties[CurveKey().property] = Curve.ED25519.value - ed25519PrivateKeyProperties[IndexKey().property] = index+1 + ed25519PrivateKeyProperties[IndexKey().property] = index + 1 val masterKey = this.apollo.createPrivateKey( secp256k1PrivateKeyProperties diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index d901fb53b..3e4010853 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -18,7 +18,6 @@ import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jose.crypto.ECDSAVerifier -import com.nimbusds.jose.crypto.Ed25519Signer import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton import com.nimbusds.jose.util.Base64URL import com.nimbusds.jwt.JWTClaimsSet @@ -28,7 +27,6 @@ import eu.europa.ec.eudi.sdjwt.KeyBindingVerifier import eu.europa.ec.eudi.sdjwt.NoSignatureValidation import eu.europa.ec.eudi.sdjwt.SdJwtVerifier import eu.europa.ec.eudi.sdjwt.recreateClaimsAndDisclosuresPerClaim -import eu.europa.ec.eudi.sdjwt.sub import io.iohk.atala.prism.didcomm.didpeer.core.toJsonElement import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod @@ -97,7 +95,6 @@ import org.hyperledger.identus.walletsdk.domain.models.keyManagement.CurvePointY import org.hyperledger.identus.walletsdk.domain.models.keyManagement.ExportableKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.KeyTypes import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PrivateKey -import org.hyperledger.identus.walletsdk.domain.models.keyManagement.PublicKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.SignableKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.TypeKey import org.hyperledger.identus.walletsdk.domain.models.keyManagement.VerifiableKey @@ -728,19 +725,17 @@ open class PolluxImpl( .build() val kid = getSigningKid(subjectDID) - - - val algorithm = if (privateKey is Secp256k1PrivateKey) { JWSAlgorithm.ES256K } else { JWSAlgorithm.EdDSA } - val header = JWSHeader.Builder(algorithm) - .keyID(kid) - .build() + val algorithm = if (privateKey is Secp256k1PrivateKey) { + JWSAlgorithm.ES256K + } else { + JWSAlgorithm.EdDSA + } + val header = JWSHeader.Builder(algorithm).keyID(kid).build() // Sign the JWT with the private key var jwsObject = SignedJWT(header, claims) // Generate a JWS header with the ES256K algorithm if (privateKey is Secp256k1PrivateKey) { // Sign the JWT with the private key - - val ecPrivateKey = privateKey.jca() as ECPrivateKey val signer = ECDSASigner( ecPrivateKey as java.security.PrivateKey, @@ -752,9 +747,6 @@ open class PolluxImpl( // Serialize the JWS object to a string return jwsObject.serialize() } else { - - - val edPrivateKey = privateKey.jca() as EdDSAPrivateKey val signer = org.bouncycastle.crypto.signers.Ed25519Signer() diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt index 118b3a4e8..305e2557a 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/edgeagent/ApolloMock.kt @@ -1,6 +1,5 @@ package org.hyperledger.identus.walletsdk.edgeagent -import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecoded import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecodedBytes import org.bouncycastle.util.encoders.Hex import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair @@ -41,7 +40,7 @@ class ApolloMock : Apollo { } override fun createPrivateKey(properties: Map): PrivateKey { - val curve = properties["curve"]; + val curve = properties["curve"] if (curve === Curve.SECP256K1.value) { return Secp256k1PrivateKey(validRawBase64UrlSecp256k1Sk) } else if (curve === Curve.ED25519.value) { From ab8ec6a53ef35949d9393e58705bd43aaa1fe953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Tue, 12 Nov 2024 13:02:47 +0100 Subject: [PATCH 21/22] fix: improvements for ed25519 sdjwt signatures --- .../identus/walletsdk/pollux/PolluxImpl.kt | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index 3e4010853..ca7d04e7f 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -18,7 +18,10 @@ import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.crypto.ECDSASigner import com.nimbusds.jose.crypto.ECDSAVerifier +import com.nimbusds.jose.crypto.Ed25519Verifier import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton +import com.nimbusds.jose.jwk.KeyUse +import com.nimbusds.jose.jwk.OctetKeyPair import com.nimbusds.jose.util.Base64URL import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT @@ -50,8 +53,13 @@ import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive +import org.bouncycastle.asn1.ASN1Encoding +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers +import org.bouncycastle.asn1.x509.AlgorithmIdentifier +import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey +import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.spec.ECNamedCurveSpec @@ -123,6 +131,8 @@ import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefiniti import org.hyperledger.identus.walletsdk.pollux.models.VerificationKeyType import org.hyperledger.identus.walletsdk.pollux.models.W3CCredential import org.hyperledger.identus.walletsdk.pollux.utils.BitString +import java.security.Security +import java.security.spec.X509EncodedKeySpec /** * Class representing the implementation of the Pollux interface. @@ -766,6 +776,7 @@ open class PolluxImpl( header.toBase64URL(), claims.toPayload().toBase64URL(), Base64URL(signature.base64UrlEncoded)) + // Serialize the JWS object to a string val jwt = jwsObject.serialize() val vc = JWTCredential.fromJwtString(jwt) @@ -773,14 +784,9 @@ open class PolluxImpl( val authenticationMethodHolder = didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } ?: throw PolluxError.VerificationUnsuccessful("Holder core properties must contain Authentication") - val ecPublicKeysHolder = - extractEcPublicKeyFromVerificationMethod(authenticationMethodHolder) - if (!verifyJWTSignatureWithEcPublicKey( - vc.id, - ecPublicKeysHolder - ) - ) { - println("Stop") + val pks = extractEdPublicKeyFromVerificationMethod(authenticationMethodHolder) + if (!verifyJWTSignatureWithEdPublicKey(vc.id, pks)) { + throw PolluxError.VerificationUnsuccessful("Invalid JWT Signature") } return jwt } @@ -1502,6 +1508,35 @@ open class PolluxImpl( return areVerified.find { it } ?: false } + internal fun verifyJWTSignatureWithEdPublicKey( + jwtString: String, + publicKeys: Array + ): Boolean { + val jwtPartsIssuer = jwtString.split(".") + if (jwtPartsIssuer.size != 3) { + throw PolluxError.InvalidJWTString("Invalid JWT string, must contain 3 parts.") + } + val jwsObject = + SignedJWT( + Base64URL(jwtPartsIssuer[0]), + Base64URL(jwtPartsIssuer[1]), + Base64URL(jwtPartsIssuer[2]) + ) + val areVerified = publicKeys.map { pk -> + + val octet = OctetKeyPair + .Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(pk.encoded)) + .keyUse(KeyUse.SIGNATURE) + .build() + + val verifiers = Ed25519Verifier(octet) + val provider = BouncyCastleProviderSingleton.getInstance() + verifiers.jcaContext.provider = provider + jwsObject.verify(verifiers) + } + return areVerified.find { it } ?: false + } + override suspend fun extractEcPublicKeyFromVerificationMethod(coreProperty: DIDDocumentCoreProperty): Array { val publicKeys = castor.getPublicKeysFromCoreProperties(arrayOf(coreProperty)) @@ -1530,6 +1565,40 @@ open class PolluxImpl( return ecPublicKeys.toTypedArray() } + private suspend fun extractEdPublicKeyFromVerificationMethod(coreProperty: DIDDocumentCoreProperty): Array { + val publicKeys = castor.getPublicKeysFromCoreProperties(arrayOf(coreProperty)) + + val edPublicKeys = publicKeys.map { publicKey -> + when (DIDDocument.VerificationMethod.getCurveByType(publicKey.getCurve())) { + Curve.ED25519 -> { + // Add BouncyCastle as a security provider if not already added + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(BouncyCastleProvider()) + } + + // Create AlgorithmIdentifier for Ed25519 + val algId = AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519) + + // Create SubjectPublicKeyInfo with AlgorithmIdentifier and public key bytes + val subjectPublicKeyInfo = SubjectPublicKeyInfo(algId, publicKey.raw) + + // Get the encoded bytes for the X509EncodedKeySpec + val x509Bytes = subjectPublicKeyInfo.getEncoded(ASN1Encoding.DER) + + // Generate EdDSAPublicKey from x509Bytes + val keyFactory = KeyFactory.getInstance("Ed25519", BouncyCastleProvider.PROVIDER_NAME) + val x509KeySpec = X509EncodedKeySpec(x509Bytes) + keyFactory.generatePublic(x509KeySpec) as EdDSAPublicKey + } + else -> { + throw Exception("Key type not supported ${publicKey.getCurve()}") + } + } + } + + return edPublicKeys.toTypedArray() + } + /** * Method to get the kId from the DID authentication property, Master key. * From 42132a397274d27ebde493d62e8fb6cda5b4ac5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribo=CC=81?= Date: Mon, 25 Nov 2024 16:01:32 +0100 Subject: [PATCH 22/22] fix: add tests for sdjwt --- .../identus/walletsdk/pollux/PolluxImpl.kt | 26 ++++++++++--- .../walletsdk/pollux/PolluxImplTest.kt | 39 +++++++++++++++++++ 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt index ca7d04e7f..feea02bbe 100644 --- a/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt +++ b/edge-agent-sdk/src/commonMain/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImpl.kt @@ -30,6 +30,9 @@ import eu.europa.ec.eudi.sdjwt.KeyBindingVerifier import eu.europa.ec.eudi.sdjwt.NoSignatureValidation import eu.europa.ec.eudi.sdjwt.SdJwtVerifier import eu.europa.ec.eudi.sdjwt.recreateClaimsAndDisclosuresPerClaim +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlEncoded import io.iohk.atala.prism.didcomm.didpeer.core.toJsonElement import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod @@ -64,9 +67,6 @@ import org.bouncycastle.jce.ECNamedCurveTable import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jce.spec.ECNamedCurveSpec import org.didcommx.didcomm.common.Typ -import org.hyperledger.identus.apollo.base64.base64UrlDecoded -import org.hyperledger.identus.apollo.base64.base64UrlDecodedBytes -import org.hyperledger.identus.apollo.base64.base64UrlEncoded import org.hyperledger.identus.apollo.utils.KMMECSecp256k1PublicKey import org.hyperledger.identus.walletsdk.apollo.helpers.gunzip import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1PrivateKey @@ -131,7 +131,10 @@ import org.hyperledger.identus.walletsdk.pollux.models.SDJWTPresentationDefiniti import org.hyperledger.identus.walletsdk.pollux.models.VerificationKeyType import org.hyperledger.identus.walletsdk.pollux.models.W3CCredential import org.hyperledger.identus.walletsdk.pollux.utils.BitString +import java.security.PublicKey import java.security.Security +import java.security.spec.EdECPoint +import java.security.spec.EdECPublicKeySpec import java.security.spec.X509EncodedKeySpec /** @@ -785,7 +788,8 @@ open class PolluxImpl( didDocHolder.coreProperties.find { it::class == DIDDocument.Authentication::class } ?: throw PolluxError.VerificationUnsuccessful("Holder core properties must contain Authentication") val pks = extractEdPublicKeyFromVerificationMethod(authenticationMethodHolder) - if (!verifyJWTSignatureWithEdPublicKey(vc.id, pks)) { + + if (!verifyJWTSignatureWithEdPublicKey( vc.id, pks ) ) { throw PolluxError.VerificationUnsuccessful("Invalid JWT Signature") } return jwt @@ -1512,6 +1516,8 @@ open class PolluxImpl( jwtString: String, publicKeys: Array ): Boolean { + Security.addProvider(BouncyCastleProvider()) + val jwtPartsIssuer = jwtString.split(".") if (jwtPartsIssuer.size != 3) { throw PolluxError.InvalidJWTString("Invalid JWT string, must contain 3 parts.") @@ -1524,11 +1530,18 @@ open class PolluxImpl( ) val areVerified = publicKeys.map { pk -> + val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(pk.encoded) + val rawPublicKeyBytes: ByteArray = subjectPublicKeyInfo.publicKeyData.bytes val octet = OctetKeyPair - .Builder(com.nimbusds.jose.jwk.Curve.Ed25519, Base64URL.encode(pk.encoded)) + .Builder( + com.nimbusds.jose.jwk.Curve.Ed25519, + Base64URL.encode(rawPublicKeyBytes) + ) .keyUse(KeyUse.SIGNATURE) .build() + + val verifiers = Ed25519Verifier(octet) val provider = BouncyCastleProviderSingleton.getInstance() verifiers.jcaContext.provider = provider @@ -1567,6 +1580,9 @@ open class PolluxImpl( private suspend fun extractEdPublicKeyFromVerificationMethod(coreProperty: DIDDocumentCoreProperty): Array { val publicKeys = castor.getPublicKeysFromCoreProperties(arrayOf(coreProperty)) + .filter { + it.getCurve().lowercase() == Curve.ED25519.toString().lowercase() + } val edPublicKeys = publicKeys.map { publicKey -> when (DIDDocument.VerificationMethod.getCurveByType(publicKey.getCurve())) { diff --git a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt index c70b3df3b..714c18359 100644 --- a/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt +++ b/edge-agent-sdk/src/commonTest/kotlin/org/hyperledger/identus/walletsdk/pollux/PolluxImplTest.kt @@ -27,6 +27,7 @@ import org.didcommx.didcomm.common.Typ import org.hyperledger.identus.apollo.base64.base64UrlDecoded import org.hyperledger.identus.apollo.derivation.MnemonicHelper import org.hyperledger.identus.walletsdk.apollo.ApolloImpl +import org.hyperledger.identus.walletsdk.apollo.utils.Ed25519KeyPair import org.hyperledger.identus.walletsdk.apollo.utils.Secp256k1KeyPair import org.hyperledger.identus.walletsdk.castor.CastorImpl import org.hyperledger.identus.walletsdk.domain.buildingblocks.Apollo @@ -1081,6 +1082,44 @@ class PolluxImplTest { ) } + @Test + fun `Test signClaims for SD-JWT including kid`() = runTest { + pollux = PolluxImpl(apollo, castor, api) + + + val masterSk = Secp256k1KeyPair.generateKeyPair(); + val authenticationSk = Ed25519KeyPair.generateKeyPair() + + val did = castor.createPrismDID( + masterSk.publicKey, + arrayOf(), + arrayOf(authenticationSk.publicKey), + ) + + val domain = "domain" + val challenge = "challenge" + val credential = JWTCredential.fromJwtString( + "eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206ZTAyZTgwOTlkNTAzNTEzNDVjNWRkODMxYTllOTExMmIzOTRhODVkMDA2NGEyZWI1OTQyOTA4MDczNGExNTliNjpDcmtCQ3JZQkVqb0tCbUYxZEdndE1SQUVTaTRLQ1hObFkzQXlOVFpyTVJJaEF1Vlljb3JmV25MMGZZdEE1dmdKSzRfLW9iM2JVRGMtdzJVT0hkTzNRRXZxRWpzS0IybHpjM1ZsTFRFUUFrb3VDZ2x6WldOd01qVTJhekVTSVFMQ3U5Tm50cXVwQmotME5DZE1BNzV6UmVCZXlhQ0pPMWFHWWVQNEJNUUhWQkk3Q2dkdFlYTjBaWEl3RUFGS0xnb0pjMlZqY0RJMU5tc3hFaUVET1dndlF4NnZSdTZ3VWI0RlljSnVhRUNqOUJqUE1KdlJwOEx3TTYxaEVUNCIsInN1YiI6ImRpZDpwcmlzbTpiZDgxZmY1NDQzNDJjMTAwNDZkZmE0YmEyOTVkNWIzNmU0Y2ZlNWE3ZWIxMjBlMTBlZTVjMjQ4NzAwNjUxMDA5OkNvVUJDb0lCRWpzS0IyMWhjM1JsY2pBUUFVb3VDZ2x6WldOd01qVTJhekVTSVFQdjVQNXl5Z3Jad2FKbFl6bDU5bTJIQURLVFhVTFBzUmUwa2dlRUh2dExnQkpEQ2c5aGRYUm9aVzUwYVdOaGRHbHZiakFRQkVvdUNnbHpaV053TWpVMmF6RVNJUVB2NVA1eXlnclp3YUpsWXpsNTltMkhBREtUWFVMUHNSZTBrZ2VFSHZ0TGdBIiwibmJmIjoxNzE1MDAwNjc0LCJleHAiOjE3MTg2MDA2NzQsInZjIjp7ImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImVtYWlsQWRkcmVzcyI6ImNyaXN0aWFuLmNhc3Ryb0Bpb2hrLmlvIiwiaWQiOiJkaWQ6cHJpc206YmQ4MWZmNTQ0MzQyYzEwMDQ2ZGZhNGJhMjk1ZDViMzZlNGNmZTVhN2ViMTIwZTEwZWU1YzI0ODcwMDY1MTAwOTpDb1VCQ29JQkVqc0tCMjFoYzNSbGNqQVFBVW91Q2dselpXTndNalUyYXpFU0lRUHY1UDV5eWdyWndhSmxZemw1OW0ySEFES1RYVUxQc1JlMGtnZUVIdnRMZ0JKRENnOWhkWFJvWlc1MGFXTmhkR2x2YmpBUUJFb3VDZ2x6WldOd01qVTJhekVTSVFQdjVQNXl5Z3Jad2FKbFl6bDU5bTJIQURLVFhVTFBzUmUwa2dlRUh2dExnQSJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sIkBjb250ZXh0IjpbImh0dHBzOlwvXC93d3cudzMub3JnXC8yMDE4XC9jcmVkZW50aWFsc1wvdjEiXSwiY3JlZGVudGlhbFN0YXR1cyI6eyJzdGF0dXNQdXJwb3NlIjoiUmV2b2NhdGlvbiIsInN0YXR1c0xpc3RJbmRleCI6MjUsImlkIjoiaHR0cDpcL1wvMTAuOTEuMTAwLjEyNjo4MDAwXC9wcmlzbS1hZ2VudFwvY3JlZGVudGlhbC1zdGF0dXNcLzUxNGU4NTI4LTRiMzgtNDc3YS1iMGU0LTMyNGJiZTIyMDQ2NCMyNSIsInR5cGUiOiJTdGF0dXNMaXN0MjAyMUVudHJ5Iiwic3RhdHVzTGlzdENyZWRlbnRpYWwiOiJodHRwOlwvXC8xMC45MS4xMDAuMTI2OjgwMDBcL3ByaXNtLWFnZW50XC9jcmVkZW50aWFsLXN0YXR1c1wvNTE0ZTg1MjgtNGIzOC00NzdhLWIwZTQtMzI0YmJlMjIwNDY0In19fQ.5OmmL5tdcRKugiHVt01PJUhp9r22zgMPPALUOB41g_1_BPHE3ezqJ2QhSmScU_EOGYcKksHftdrvfoND65nSjw" + ) + val signedClaims = pollux.signClaims( + subjectDID = did, + privateKey = authenticationSk.privateKey, + domain = domain, + challenge = challenge, + credential = credential + ) + assertTrue(signedClaims.contains(".")) + val splits = signedClaims.split(".") + val header = splits[0].base64UrlDecoded + val json = Json.parseToJsonElement(header) + assertTrue(json.jsonObject.containsKey("kid")) + val kid = json.jsonObject["kid"]!!.jsonPrimitive.content + assertEquals( + "did:prism:cd6cf9f94a43c53e286b0f2015c0083701350a694f52a22ee02e3bd29d93eba9:CrQBCrEBEjsKB21hc3RlcjAQAUouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xJDCg9hdXRoZW50aWNhdGlvbjAQBEouCglzZWNwMjU2azESIQKJIokEe_iKRGsr0f2EEa1JHGm59g0qP7QMtw6FcVxW9xotCgojZGlkY29tbS0xEhBESURDb21tTWVzc2FnaW5nGg1kaWQ6cGVlcjp0ZXN0#authentication0", + kid + ) + } + @Test fun `Test SD-JWT createPresentationDefinitionRequest`() = runTest { pollux = PolluxImpl(apollo, castor, api)