Skip to content

Commit

Permalink
bump web5-kt version to 0.13.0 (#196)
Browse files Browse the repository at this point in the history
* fixing tests. removing ion signing test because we yeeted ion from our supported ion methods.

* Add `replyTo` as a submitcallback function param (#197)

* adding replyTo as submitcallback function param

* adding null fields
  • Loading branch information
jiyoonie9 authored Mar 11, 2024
1 parent fc13e16 commit ad30c6a
Show file tree
Hide file tree
Showing 12 changed files with 53 additions and 138 deletions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ com_networknt = "1.0.87"
com_squareup_okhttp = "4.12.0"
de_fxlae = "0.2.0"
io_ktor = "2.3.7"
xyz_block_web5 = "0.12.0"
xyz_block_web5 = "0.13.0"

[libraries]
comFasterXmlJacksonModuleKotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "com_fasterxml_jackson" }
Expand Down
14 changes: 6 additions & 8 deletions httpclient/src/main/kotlin/tbdex/sdk/httpclient/RequestToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ package tbdex.sdk.httpclient
import com.nimbusds.jose.JOSEObjectType
import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.jwk.JWK
import com.nimbusds.jose.util.Base64URL
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import foundation.identity.did.VerificationMethod
import web5.sdk.common.Convert
import web5.sdk.dids.Did
import web5.sdk.dids.DidResolvers
import web5.sdk.dids.findAssertionMethodById
import web5.sdk.dids.didcore.VerificationMethod
import java.time.Instant
import java.util.Date
import java.util.UUID
Expand Down Expand Up @@ -46,18 +44,18 @@ object RequestToken {
.didDocument?.findAssertionMethodById(assertionMethodId)
?: throw RequestTokenCreateException("Assertion method not found")


// TODO: ensure that publicKeyJwk is not null
val publicKeyJwk = JWK.parse(assertionMethod.publicKeyJwk)
val publicKeyJwk = assertionMethod.publicKeyJwk
check(publicKeyJwk != null) { "publicKeyJwk is null" }
val keyAlias = did.keyManager.getDeterministicAlias(publicKeyJwk)

// TODO: figure out how to make more reliable since algorithm is technically not a required property of a JWK
val algorithm = publicKeyJwk.algorithm
val jwsAlgorithm = JWSAlgorithm.parse(algorithm.toString())

val kid = when (assertionMethod.id.isAbsolute) {
true -> assertionMethod.id.toString()
false -> "${did.uri}${assertionMethod.id}"
val kid = when (assertionMethod.id.startsWith("#")) {
true -> "${did.uri}${assertionMethod.id}"
false -> assertionMethod.id
}

val jwtHeader = JWSHeader.Builder(jwsAlgorithm)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import tbdex.sdk.httpclient.models.TbdexResponseException
import tbdex.sdk.protocol.Validator
import tbdex.sdk.protocol.models.Close
import tbdex.sdk.protocol.models.Message
import tbdex.sdk.protocol.models.MessageKind
import tbdex.sdk.protocol.models.Offering
import tbdex.sdk.protocol.models.Order
import tbdex.sdk.protocol.models.Rfq
Expand Down Expand Up @@ -89,7 +88,6 @@ object TbdexHttpClient {
* Send RFQ message to the PFI.
*
* @param rfq The RFQ to send
* @param replyTo The callback URL for PFI to send messages to.
*
* @throws TbdexResponseException for response errors.
*/
Expand Down Expand Up @@ -150,9 +148,9 @@ object TbdexHttpClient {
}

/**
* Send Order message to the PFI.
* Send Close message to the PFI.
*
* @param order The Order to send
* @param close The Close to send
*
* @throws TbdexResponseException for response errors.
*/
Expand Down
6 changes: 3 additions & 3 deletions httpclient/src/main/kotlin/tbdex/sdk/httpclient/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package tbdex.sdk.httpclient
import web5.sdk.dids.DidResolvers

/**
* Get pfi service endpoint
* Get pfi service endpoint. Grabs the first service endpoint.
*
* @param pfiDid
* @return
*/
fun getPfiServiceEndpoint(pfiDid: String): String {
val didResolutionResult = DidResolvers.resolve(pfiDid)
val service = didResolutionResult.didDocument?.services?.find { it.isType("PFI") }
val service = didResolutionResult.didDocument?.service?.find { it.type == "PFI" }

requireNotNull(service) {
"DID does not have service of type PFI"
}

return service.serviceEndpoint.toString()
return service.serviceEndpoint.first().toString()
}
13 changes: 6 additions & 7 deletions httpclient/src/test/kotlin/tbdex/sdk/httpclient/E2ETest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package tbdex.sdk.httpclient

import com.nimbusds.jose.jwk.JWK
import foundation.identity.did.Service
import org.junit.jupiter.api.Disabled
import tbdex.sdk.httpclient.models.GetExchangesFilter
import tbdex.sdk.httpclient.models.GetOfferingsFilter
Expand All @@ -16,11 +15,11 @@ import tbdex.sdk.protocol.models.SelectedPaymentMethod
import web5.sdk.credentials.VerifiableCredential
import web5.sdk.crypto.InMemoryKeyManager
import web5.sdk.dids.Did
import web5.sdk.dids.PublicKeyPurpose
import web5.sdk.dids.didcore.Purpose
import web5.sdk.dids.didcore.Service
import web5.sdk.dids.methods.dht.CreateDidDhtOptions
import web5.sdk.dids.methods.dht.DidDht
import web5.sdk.dids.methods.key.DidKey
import java.net.URI
import java.util.UUID
import kotlin.test.Test

Expand Down Expand Up @@ -49,15 +48,15 @@ class E2ETest {
"x": "i6cnsuH4JTBMXKbseg28Hi3w4Xp13E85UwnSW3ZgYk8"
}"""
),
arrayOf(PublicKeyPurpose.AUTHENTICATION),
listOf(Purpose.Authentication),
UUID.randomUUID().toString()
)
),
services = listOf(
Service.builder()
.id(URI.create("#pfi"))
Service.Builder()
.id("#pfi")
.type("PFI")
.serviceEndpoint("http://localhost:9000")
.serviceEndpoint(listOf("http://localhost:9000"))
.build()
),
publish = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ import tbdex.sdk.httpclient.models.ErrorDetail
import tbdex.sdk.httpclient.models.TbdexResponseException
import tbdex.sdk.protocol.models.Close
import tbdex.sdk.protocol.models.CloseData
import tbdex.sdk.protocol.models.Message
import tbdex.sdk.protocol.models.Order
import tbdex.sdk.protocol.models.Quote
import tbdex.sdk.protocol.models.Rfq
import tbdex.sdk.protocol.serialization.Json
import web5.sdk.crypto.InMemoryKeyManager
import web5.sdk.dids.methods.ion.CreateDidIonOptions
import web5.sdk.dids.methods.ion.DidIon
import web5.sdk.dids.methods.ion.models.Service
import web5.sdk.dids.methods.jwk.DidJwk
import web5.sdk.dids.didcore.Service
import web5.sdk.dids.methods.dht.CreateDidDhtOptions
import web5.sdk.dids.methods.dht.DidDht
import web5.sdk.dids.methods.key.DidKey
import java.net.HttpURLConnection
import kotlin.test.Test
Expand All @@ -35,10 +33,10 @@ class TbdexHttpClientTest {
private lateinit var server: MockWebServer
private val aliceDid = DidKey.create(InMemoryKeyManager())

private val pfiDid = DidIon.create(
private val pfiDid = DidDht.create(
InMemoryKeyManager(),
CreateDidIonOptions(
servicesToAdd = listOf(Service("123", "PFI", "http://localhost:9000"))
CreateDidDhtOptions(
services = listOf(Service("123", "PFI", listOf("http://localhost:9000")))
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ suspend fun createExchange(

val offering: Offering
try {
offering = offeringsApi.getOffering(message.data.offeringId.toString())
offering = offeringsApi.getOffering(message.data.offeringId)

message.verifyOfferingRequirements(offering)
} catch (e: NoSuchElementException) {
Expand All @@ -90,7 +90,7 @@ suspend fun createExchange(
}

try {
callback.invoke(call, message, offering)
callback.invoke(call, message, offering, replyTo)
} catch (e: CallbackError) {
call.respond(e.statusCode, ErrorResponse(e.details))
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ suspend fun submitClose(
}

try {
callback.invoke(call, message, null)
callback.invoke(call, message, null, null)
} catch (e: CallbackError) {
call.respond(e.statusCode, ErrorResponse(e.details))
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ suspend fun submitOrder(
}

try {
callback.invoke(call, message, null)
callback.invoke(call, message, null, null)
} catch (e: CallbackError) {
call.respond(e.statusCode, ErrorResponse(e.details))
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ typealias GetCallback = suspend (ApplicationCall, Filter) -> Any
* @param ApplicationCall The Ktor application call object representing the incoming HTTP request.
* @param Message the message received in the request to be processed further by the callback function
* @param Offering The offering associated with the submitted message.
* @param ReplyTo The replyTo URL if provided in the CreateExchange request
*/
typealias SubmitCallback = suspend (ApplicationCall, Message, Offering?) -> Unit
typealias SubmitCallback = suspend (ApplicationCall, Message, Offering?, String?) -> Unit

/**
* Enum representing the kinds of messages that can be submitted.
Expand Down
73 changes: 23 additions & 50 deletions protocol/src/main/kotlin/tbdex/sdk/protocol/CryptoUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSHeader
import com.nimbusds.jose.JWSObject
import com.nimbusds.jose.Payload
import com.nimbusds.jose.jwk.JWK
import foundation.identity.did.DIDURL
import foundation.identity.did.VerificationMethod
import org.erdtman.jcs.JsonCanonicalizer
import tbdex.sdk.protocol.models.Data
import tbdex.sdk.protocol.models.Metadata
Expand All @@ -15,6 +12,7 @@ import web5.sdk.common.Convert
import web5.sdk.crypto.Crypto
import web5.sdk.dids.Did
import web5.sdk.dids.DidResolvers
import web5.sdk.dids.didcore.DidUri
import java.security.MessageDigest
import java.security.SignatureException

Expand Down Expand Up @@ -55,57 +53,56 @@ object CryptoUtils {
}

val verificationMethodId = jws.header.keyID
val parsedDidUrl = DIDURL.fromString(verificationMethodId) // validates vm id which is a DID URL
val parsedDidUrl = DidUri.parse(verificationMethodId) // validates vm id which is a DID URL

val signingDid = parsedDidUrl.uriWithoutFragment.toString()
val signingDid = parsedDidUrl.uri
if (signingDid != did) {
throw SignatureException(
"Signature verification failed: Was not signed by the expected DID. " +
"Expected DID: $did. Actual DID: $signingDid"
)
}

val didResolutionResult = DidResolvers.resolve(parsedDidUrl.did.didString)
val didResolutionResult = DidResolvers.resolve(parsedDidUrl.uri)
if (didResolutionResult.didResolutionMetadata.error != null) {
throw SignatureException(
"Signature verification failed: " +
"Failed to resolve DID ${parsedDidUrl.did.didString}. " +
"Failed to resolve DID ${parsedDidUrl.url}. " +
"Error: ${didResolutionResult.didResolutionMetadata.error}"
)
}

// Create a set of possible id matches. The DID spec allows for an id to be the entire `did#fragment`
// or just `#fragment`. See: https://www.w3.org/TR/did-core/#relative-did-urls.
// Using a set for fast string comparison. DIDs can be long.
val verificationMethodIds = setOf(parsedDidUrl.didUrlString, "#${parsedDidUrl.fragment}")
val assertionMethods = didResolutionResult.didDocument?.assertionMethodVerificationMethodsDereferenced
val assertionMethod = assertionMethods?.firstOrNull {
val id = it.id.toString()
val verificationMethodIds = setOf(parsedDidUrl.url, "#${parsedDidUrl.fragment}")
val assertionMethodIds = didResolutionResult.didDocument?.assertionMethod
val assertionMethodId = assertionMethodIds?.firstOrNull { id ->
verificationMethodIds.contains(id)
}

require(assertionMethod != null) {
require(assertionMethodId != null) {
throw SignatureException(
"Signature verification failed: Expected kid in JWS header to dereference " +
"a DID Document Verification Method with an Assertion verification relationship"
)
}

val assertionVerificationMethod = didResolutionResult.didDocument?.findAssertionMethodById(assertionMethodId)

require(
(assertionMethod.isType("JsonWebKey2020") || assertionMethod.isType("JsonWebKey"))
&& assertionMethod.publicKeyJwk != null
(assertionVerificationMethod != null &&
(assertionVerificationMethod.isType("JsonWebKey2020") || assertionVerificationMethod.isType("JsonWebKey")))
&& assertionVerificationMethod.publicKeyJwk != null
) {
throw SignatureException(
"Signature verification failed: Expected kid in JWS header to dereference " +
"a DID Document Verification Method of type JsonWebKey2020 with a publicKeyJwk"
)
}

val publicKeyMap = assertionMethod.publicKeyJwk
val publicKeyJwk = JWK.parse(publicKeyMap)

Crypto.verify(
publicKey = publicKeyJwk,
publicKey = assertionVerificationMethod.publicKeyJwk!!,
signedPayload = jws.signingInput,
signature = jws.signature.decode()
)
Expand All @@ -120,19 +117,21 @@ object CryptoUtils {
* @return The signed payload as a detached payload JWT (JSON Web Token).
*/
fun sign(did: Did, payload: ByteArray, assertionMethodId: String? = null): String {
val assertionMethod = getAssertionMethod(did, assertionMethodId)
val didResolutionResult = DidResolvers.resolve(did.uri)

val assertionMethod = didResolutionResult.didDocument?.findAssertionMethodById(assertionMethodId)

// TODO: ensure that publicKeyJwk is not null
val publicKeyJwk = JWK.parse(assertionMethod.publicKeyJwk)
val keyAlias = did.keyManager.getDeterministicAlias(publicKeyJwk)

check(assertionMethod?.publicKeyJwk != null) { "publicKeyJwk is null" }
val keyAlias = did.keyManager.getDeterministicAlias(assertionMethod?.publicKeyJwk!!)

val publicKey = did.keyManager.getPublicKey(keyAlias)
val algorithm = publicKey.algorithm
val jwsAlgorithm = JWSAlgorithm.parse(algorithm.toString())

val selectedAssertionMethodId = when {
assertionMethod.id.isAbsolute -> assertionMethod.id.toString()
else -> "${did.uri}${assertionMethod.id}"
assertionMethod.id.startsWith("#") -> "${did.uri}${assertionMethod.id}"
else -> assertionMethod.id
}

val jwsHeader = JWSHeader.Builder(jwsAlgorithm)
Expand All @@ -151,30 +150,4 @@ object CryptoUtils {

return "$base64UrlEncodedHeader..$base64UrlEncodedSignature"
}

/**
* Retrieves the desired assertion verification method from a DID (Decentralized Identifier) based on the provided
* assertion method identifier.
*
* This function resolves the DID, extracts the assertion methods from the DID Document, and returns the specific
* assertion verification method associated with the provided `assertionMethodId`, if specified. If
* `assertionMethodId` is not provided, it returns the first assertion verification method found in the DID Document.
*
* @param did The Decentralized Identifier (DID) to retrieve the assertion method from.
* @param assertionMethodId The identifier of the specific assertion verification method to retrieve (optional).
* @return The assertion verification method corresponding to the provided `assertionMethodId` or the first assertion
* verification method found in the DID Document.
* @throws SignatureException If the specified `assertionMethodId` is not found in the DID Document.
*/
fun getAssertionMethod(did: Did, assertionMethodId: String?): VerificationMethod {
val didResolutionResult = DidResolvers.resolve(did.uri)
val assertionMethods = didResolutionResult.didDocument?.assertionMethodVerificationMethodsDereferenced

val assertionMethod: VerificationMethod = when {
assertionMethodId != null -> assertionMethods?.find { it.id.toString() == assertionMethodId }
else -> assertionMethods?.firstOrNull()
} ?: throw SignatureException("assertion method $assertionMethodId not found")

return assertionMethod
}
}
Loading

0 comments on commit ad30c6a

Please sign in to comment.