Skip to content

Commit

Permalink
Removing did-common-java dependency (#248)
Browse files Browse the repository at this point in the history
* removing did common java dep

* adding new types. wip - some difference between web5-go and did-common-java in comments

* adding ktdocs. changing out id of type URI for id of type String in diddoc verif method and service types

* removing todos and just using string for id instead of uri

* pulling in Json from tbdex-kt for stringifying instead of relying on jsonldobject inherited methods from did common java. replacing didurl with a verificationMethodId splitting on #

* goodbye didion. cleaning up diddocument. renaming some methods. more questions as todos

* cleaning up. pulling documentmetadata back in from previously deleted didion stuff

* changing Did to BaseDid to reduce confusion between the Did that all did methods inherit from, and the new DID type

* using verificationMethod() in the builder class, removing all purpose specific methods in favor of the generic one

* addressing some todos

* reverting renaming, to be done later

* removing duplicate documentmetadata

* hella kt docs

* stuff from pairing

* fixing tests

* pulling in work done in PR#225 by @grahnj as part of removing did ion reference. in the middle of refactoring did dht slightly. only 7-8 tests failing now...

* wrote tests for diddocument

* renaming Did.kt file to DidUri.kt to be consistent with current naming. making verificationMethod a set to avoid duplication. 1 test remains to be fixed

* wrote tostring for service. moving util method to dids package. added a todo to fix a test

* removing todos, pulled out util method to separate file

* addressing code review comments

* fixing test vectors

* added tests for service verification and did uri. one test failing for did uri

* fixed a test, but with a todo to discuss

* removed printlns

* adding more invalid did test cases

* addressing tests

* quietly committing this before the tests find out they actually wanted to fail

* addressing code review comments

* adding more tests to please codecov

* moving Json out to commons package

* fixing import errors

* changing context field to be a list, changing test vectors to comply with this change

* update web5-spec commit to be latest in main


Co-authored-by: Moe Jangda <[email protected]>
  • Loading branch information
Jiyoon Koo and mistermoe authored Mar 6, 2024
1 parent 99f9f60 commit 2e4946d
Show file tree
Hide file tree
Showing 53 changed files with 1,797 additions and 2,546 deletions.
47 changes: 0 additions & 47 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ allprojects {
force("com.google.guava:guava:32.0.0-android")
// Addresses https://github.com/TBD54566975/web5-kt/issues/244
force("com.squareup.okio:okio:3.6.0")
// Addresses https://github.com/TBD54566975/web5-kt/issues/257
force("com.nimbusds:nimbus-jose-jwt:9.37.2")
}
}
}
Expand Down Expand Up @@ -101,60 +99,15 @@ subprojects {
jvmTarget = "1.8"
}

sourceSets {
create("intTest") {
compileClasspath += sourceSets.main.get().output
runtimeClasspath += sourceSets.main.get().output
}
}

val intTestImplementation by configurations.getting {
extendsFrom(configurations.implementation.get())
}
val intTestRuntimeOnly by configurations.getting

configurations["intTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())

dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
detektPlugins("com.github.TBD54566975:tbd-detekt-rules:v0.0.2")

testImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

intTestImplementation(kotlin("test"))
intTestImplementation("org.junit.jupiter:junit-jupiter:5.9.2")
intTestRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

idea {
module {
testSources.from(sourceSets["intTest"].java.srcDirs)
testSources.from(sourceSets["intTest"].kotlin.srcDirs)
}
}

val integrationTest = task<Test>("integrationTest") {
description = "Runs integration tests."
group = "verification"

testClassesDirs = sourceSets["intTest"].output.classesDirs
classpath = sourceSets["intTest"].runtimeClasspath
shouldRunAfter("test")

useJUnitPlatform()

testLogging {
events("passed", "skipped", "failed", "standardOut", "standardError")
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}

tasks.check { dependsOn(integrationTest) }

detekt {
config.setFrom("$rootDir/config/detekt.yml")
}
Expand Down
1 change: 1 addition & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
// Project

// Implementation
implementation(libs.comFasterXmlJacksonModuleKotlin)

// Test
/**
Expand Down
46 changes: 46 additions & 0 deletions common/src/main/kotlin/web5/sdk/common/Json.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package web5.sdk.common

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.ObjectWriter
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.registerKotlinModule

/**
* A singleton for json serialization/deserialization, shared across the SDK as ObjectMapper instantiation
* is an expensive operation.
* - Serialize ([stringify])
*
* ### Example Usage:
* ```kotlin
* val offering = Json.objectMapper.readValue<Offering>(payload)
*
* val jsonString = Json.stringify(myObject)
*
* val node = Json.parse(payload)
* ```
*/
public object Json {
/**
* The Jackson object mapper instance, shared across the lib.
*
* It must be public in order for typed parsing to work as we cannot use reified types for Java interop.
*/
public val jsonMapper: ObjectMapper = ObjectMapper()
.registerKotlinModule()
.findAndRegisterModules()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)

private val objectWriter: ObjectWriter = jsonMapper.writer()

/**
* Converts a kotlin object to a json string.
*
* @param obj The object to stringify.
* @return json string.
*/
public fun stringify(obj: Any): String {
return objectWriter.writeValueAsString(obj)
}
}
3 changes: 2 additions & 1 deletion config/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ complexity:
CyclomaticComplexMethod:
active: true
ignoreSingleWhenExpression: true
threshold: 25
LongParameterList:
constructorThreshold: 10
MethodOverloading:
Expand Down Expand Up @@ -82,7 +83,7 @@ formatting:
MaximumLineLength:
active: true
ImportOrdering:
active: true
active: false

naming:
MemberNameEqualsClassName:
Expand Down
1 change: 0 additions & 1 deletion credentials/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ dependencies {
implementation(libs.comNetworkntJsonSchemaValidator)
implementation(libs.comNfeldJsonpathkt)
implementation(libs.comNimbusdsJoseJwt)
implementation(libs.decentralizedIdentityDidCommonJava)
implementation(libs.bundles.ioKtorForCredentials)

// Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
*
* @param did The [Did] used to sign the credential.
* @param assertionMethodId An optional identifier for the assertion method that will be used for verification of the
* produces signature.
* produced signature.
* @return The JWT representing the signed verifiable credential.
*
* Example:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ public typealias VpDataModel = com.danubetech.verifiablecredentials.VerifiablePr
*
* @property vpDataModel The [vpDataModel] instance representing the core data model of a verifiable presentation.
*/
@Suppress("UNCHECKED_CAST")
public class VerifiablePresentation internal constructor(public val vpDataModel: VpDataModel) {

public val verifiableCredential: List<String>
get() = vpDataModel.toMap().get("verifiableCredential") as List<String>
get() = vpDataModel.toMap()["verifiableCredential"] as List<String>

public val holder: String
get() = vpDataModel.holder.toString()
Expand Down
67 changes: 41 additions & 26 deletions credentials/src/main/kotlin/web5/sdk/credentials/util/JwtUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ package web5.sdk.credentials.util
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.JWTParser
import com.nimbusds.jwt.SignedJWT
import foundation.identity.did.DIDURL
import web5.sdk.common.Convert
import web5.sdk.crypto.Crypto
import web5.sdk.crypto.Jwa
import web5.sdk.dids.Did
import web5.sdk.dids.DidResolvers
import web5.sdk.dids.didcore.DidUri
import web5.sdk.dids.exceptions.DidResolutionException
import web5.sdk.dids.findAssertionMethodById
import web5.sdk.dids.exceptions.PublicKeyJwkMissingException
import java.net.URI
import java.security.SignatureException

private const val JsonWebKey2020 = "JsonWebKey2020"
private const val JsonWebKey = "JsonWebKey"
private const val JSON_WEB_KEY_2020 = "JsonWebKey2020"
private const val JSON_WEB_KEY = "JsonWebKey"

/**
* Util class for common shared JWT methods.
Expand Down Expand Up @@ -56,16 +55,15 @@ public object JwtUtil {

val assertionMethod = didDocument.findAssertionMethodById(assertionMethodId)

// TODO: ensure that publicKeyJwk is not null
val publicKeyJwk = JWK.parse(assertionMethod.publicKeyJwk)
val publicKeyJwk = assertionMethod.publicKeyJwk ?: throw PublicKeyJwkMissingException("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()
val kid = when (URI.create(assertionMethod.id).isAbsolute) {
true -> assertionMethod.id
false -> "${did.uri}${assertionMethod.id}"
}

Expand Down Expand Up @@ -110,42 +108,57 @@ public object JwtUtil {
}

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

val didResolutionResult = DidResolvers.resolve(parsedDidUrl.did.didString)
val didResolutionResult = DidResolvers.resolve(didUri.url)
if (didResolutionResult.didResolutionMetadata.error != null) {
throw SignatureException(
"Signature verification failed: " +
"Failed to resolve DID ${parsedDidUrl.did.didString}. " +
"Failed to resolve DID ${didUri.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 lonnng.
val verificationMethodIds = setOf(parsedDidUrl.didUrlString, "#${parsedDidUrl.fragment}")
val assertionMethods = didResolutionResult.didDocument?.assertionMethodVerificationMethodsDereferenced
val assertionMethod = assertionMethods?.firstOrNull {
val id = it.id.toString()
verificationMethodIds.contains(id)
}
?: throw SignatureException(
val verificationMethodIds = setOf(
didUri.url,
"#${didUri.fragment}"
)

didResolutionResult.didDocument?.assertionMethod?.firstOrNull {
verificationMethodIds.contains(it)
} ?: throw SignatureException(
"Signature verification failed: Expected kid in JWS header to dereference " +
"a DID Document Verification Method with an Assertion verification relationship"
)

// TODO: this will be cleaned up as part of BearerDid PR
val assertionVerificationMethod = didResolutionResult
.didDocument
?.verificationMethod
?.find { verificationMethodIds.contains(it.id) }

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

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

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

val publicKeyJwk =
assertionVerificationMethod.publicKeyJwk ?: throw PublicKeyJwkMissingException("publicKeyJwk is null")
val toVerifyBytes = jwt.signingInput
val signatureBytes = jwt.signature.decode()

Expand All @@ -155,4 +168,6 @@ public object JwtUtil {
signatureBytes
)
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,16 @@ class PresentationExchangeTest {
.registerKotlinModule()
.setSerializationInclusion(JsonInclude.Include.NON_NULL)

@Suppress("MaximumLineLength")
val sanctionsVcJwt =
"eyJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtrdU5tSmF0ZUNUZXI1V0JycUhCVUM0YUM3TjlOV1NyTURKNmVkQXY1V0NmMiIsInN1YiI6ImRpZDprZXk6ejZNa2t1Tm1KYXRlQ1RlcjVXQnJxSEJVQzRhQzdOOU5XU3JNREo2ZWRBdjVXQ2YyIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwiaWQiOiIxNjk4NDIyNDAxMzUyIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlNhbmN0aW9uc0NyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1ra3VObUphdGVDVGVyNVdCcnFIQlVDNGFDN045TldTck1ESjZlZEF2NVdDZjIiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEwLTI3VDE2OjAwOjAxWiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1ra3VObUphdGVDVGVyNVdCcnFIQlVDNGFDN045TldTck1ESjZlZEF2NVdDZjIiLCJiZWVwIjoiYm9vcCJ9fX0.Xhd9nDdkGarYFr6FP7wqsgj5CK3oGTfKU2LHNMvFIsvatgYlSucShDPI8uoeJ_G31uYPke-LJlRy-WVIhkudDg"
"eyJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtrdU5tSmF0ZUNUZXI1V0JycUhCVUM0YUM3TjlOV1NyTUR" +
"KNmVkQXY1V0NmMiIsInN1YiI6ImRpZDprZXk6ejZNa2t1Tm1KYXRlQ1RlcjVXQnJxSEJVQzRhQzdOOU5XU3JNREo2Z" +
"WRBdjVXQ2YyIiwidmMiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjE" +
"iXSwiaWQiOiIxNjk4NDIyNDAxMzUyIiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIlNhbmN0aW9uc0NyZ" +
"WRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1ra3VObUphdGVDVGVyNVdCcnFIQlVDNGFDN045TldTck1ESjZ" +
"lZEF2NVdDZjIiLCJpc3N1YW5jZURhdGUiOiIyMDIzLTEwLTI3VDE2OjAwOjAxWiIsImNyZWRlbnRpYWxTdWJqZWN0I" +
"jp7ImlkIjoiZGlkOmtleTp6Nk1ra3VObUphdGVDVGVyNVdCcnFIQlVDNGFDN045TldTck1ESjZlZEF2NVdDZjIiLCJ" +
"iZWVwIjoiYm9vcCJ9fX0.Xhd9nDdkGarYFr6FP7wqsgj5CK3oGTfKU2LHNMvFIsvatgYlSucShDPI8uoeJ_G31uYPk" +
"e-LJlRy-WVIhkudDg"


private fun readPd(path: String): String {
Expand Down
Loading

0 comments on commit 2e4946d

Please sign in to comment.