Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bump web5-kt version to 0.13.0 #196

Merged
merged 2 commits into from
Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
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.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.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 @@
}

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}. " +

Check warning on line 70 in protocol/src/main/kotlin/tbdex/sdk/protocol/CryptoUtils.kt

View check run for this annotation

Codecov / codecov/patch

protocol/src/main/kotlin/tbdex/sdk/protocol/CryptoUtils.kt#L70

Added line #L70 was not covered by tests
"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 @@
* @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 @@

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
}
}
52 changes: 0 additions & 52 deletions protocol/src/test/kotlin/tbdex/sdk/protocol/IonSigningTest.kt

This file was deleted.

Loading