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

Implementing BearerDid / PortableDid, exposing our own JWS, JWT, JWK types #262

Merged
merged 50 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9eca633
create bearerdid and portable did, keyexporter and keyimporter
jiyoonie9 Mar 7, 2024
12cb845
changing Did to ChangemeDid to make it easier to spot and replace w B…
jiyoonie9 Mar 7, 2024
28e683a
removing didmethod interface
jiyoonie9 Mar 7, 2024
0106920
trying on BearerDID for did jwk
jiyoonie9 Mar 7, 2024
9c5fbbf
removing create options
jiyoonie9 Mar 9, 2024
f348127
getting did:dht and did:key to return BearerDID. next step: figure ou…
jiyoonie9 Mar 9, 2024
cb309ee
moved bearerdid import() to companion object so i can call bearerdid.…
jiyoonie9 Mar 10, 2024
1f32e5a
updated codeowners doc
jiyoonie9 Mar 10, 2024
a3ac9b5
changing type names to be true CamelCase. implemented Jws methods and…
jiyoonie9 Mar 11, 2024
90fa517
wrote jwt class. modify jws class to include builder for jwsheader
jiyoonie9 Mar 11, 2024
584a36b
sub out JwtUtils.sign() for Jwt.sign()
jiyoonie9 Mar 11, 2024
a9e1494
implemented jwt. removed jwtutil class
jiyoonie9 Mar 12, 2024
d387f2c
using hand rolled JwsHeader and JwtClaimsSet instead of nimbusds
jiyoonie9 Mar 12, 2024
b681b7c
adding jwk impl, in progress
jiyoonie9 Mar 12, 2024
33852b9
adding jose package. fixing some todos
jiyoonie9 Mar 14, 2024
9a77e89
goodbye, changemedid
jiyoonie9 Mar 14, 2024
9945824
updating tests to test import instead of load
jiyoonie9 Mar 14, 2024
f522197
wrote kt docs, fixed didresolvers, removed resolvedidoptions because …
jiyoonie9 Mar 14, 2024
e92dbd1
removing unnecessary tests
jiyoonie9 Mar 14, 2024
275d6f8
fixing tests
jiyoonie9 Mar 15, 2024
e2a3cb7
requiring keytype and curve when building Jwk
jiyoonie9 Mar 18, 2024
f0fbc80
adding default keyUse of sig to Jwk when computing public key.
jiyoonie9 Mar 18, 2024
46e3d5e
fixing some tests
jiyoonie9 Mar 18, 2024
f4c418c
adding '$.misc.vc....' path to all test vector jsons
jiyoonie9 Mar 19, 2024
a3e7688
fixing test vectors for credentials package tests
jiyoonie9 Mar 19, 2024
81c470d
reverting back adding $.misc.vc... for valid paths, instead using jwt…
jiyoonie9 Mar 19, 2024
fca2488
removing todos, removing extraneous Convert() call in Jwt.decode()
jiyoonie9 Mar 20, 2024
f56860c
removing jwk serializer/deserializer
jiyoonie9 Mar 21, 2024
129483b
tests pass. writing tests for jwt in process. jwk still needs tests w…
jiyoonie9 Mar 21, 2024
68d9c6b
Merge branch 'main' of github.com:TBD54566975/web5-kt into 234-did-impl
jiyoonie9 Mar 21, 2024
0b7dc33
fixing tests
jiyoonie9 Mar 21, 2024
149ed9a
new web5-spec sha
jiyoonie9 Mar 21, 2024
711f9d7
adding more jwttest
jiyoonie9 Mar 22, 2024
2f3c98d
added jwktest
jiyoonie9 Mar 22, 2024
c5d3b91
fixing detekt error
jiyoonie9 Mar 22, 2024
c1ea33d
adding bearerdid tests
jiyoonie9 Mar 22, 2024
eb52fd3
removing portabledidtest class because there is no method inside port…
jiyoonie9 Mar 22, 2024
5cdfec3
fixing detekt error
jiyoonie9 Mar 22, 2024
3937376
moving nimbusds lib to implementation instead of api
jiyoonie9 Mar 22, 2024
cda57a0
adding wording around not supporting did web creation
jiyoonie9 Mar 23, 2024
58db293
adding uri as a property to BearerDid as it was missed. addressing co…
jiyoonie9 Mar 24, 2024
55e652c
Merge branch 'main' of github.com:TBD54566975/web5-kt into 234-did-impl
jiyoonie9 Mar 24, 2024
5ab978d
changing how portabledid is constructed in vc test vector test
jiyoonie9 Mar 27, 2024
030678d
addressing review comments
jiyoonie9 Mar 27, 2024
6004707
Update dids/src/test/kotlin/web5/sdk/dids/methods/jwk/DidJwkTest.kt
Mar 27, 2024
b472990
making error in testvectorinput to default to false, added portabledi…
jiyoonie9 Mar 27, 2024
aa90cd4
Merge branch '234-did-impl' of github.com:TBD54566975/web5-kt into 23…
jiyoonie9 Mar 27, 2024
c5bba53
adding comment about supporting keys that use ECC
jiyoonie9 Mar 27, 2024
1d7e0a5
reverting back to include portabledid concept in vc create test vecto…
jiyoonie9 Mar 27, 2024
870cc12
bumping web5-spec commit
jiyoonie9 Mar 27, 2024
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 CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# The format is described: https://github.blog/2017-07-06-introducing-code-owners/

# These owners will be the default owners for everything in the repo.
* @amika-sq @mistermoe @nitro-neal @tomdaffurn @phoebe-lew @diehuxx @kirahsapong @jiyoontbd @frankhinek
* @mistermoe @nitro-neal @tomdaffurn @phoebe-lew @diehuxx @kirahsapong @jiyoontbd @frankhinek

/crypto/src/main/kotlin/web5/sdk/crypto/AwsKeyManager.kt @tomdaffurn
# -----------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
api(project(":credentials"))
api(project(":crypto"))
api(project(":dids"))
api(project(":jose"))

detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.+")
}
Expand Down
7 changes: 4 additions & 3 deletions common/src/main/kotlin/web5/sdk/common/Convert.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public val B64URL_DECODER: Base64.Decoder = Base64.getUrlDecoder()
* ```
* // Example 1: Convert a ByteArray to a Base64Url encoded string without padding
* val byteArray = byteArrayOf(1, 2, 3)
* val base64Url = Convert(byteArray).toBase64Url(padding = false)
* val base64Url = Convert(byteArray).toBase64Url() // same as .toBase64Url(padding = false)
* println(base64Url) // Output should be a Base64Url encoded string without padding
*
* // Example 2: Convert a Base64Url encoded string to a ByteArray
Expand All @@ -68,13 +68,14 @@ public class Convert<T>(private val value: T, private val kind: EncodingFormat?
/**
* Converts the [value] to a Base64Url-encoded string.
*
* @param padding Determines whether the resulting Base64 string should be padded or not. Default is true.
* @param padding Determines whether the resulting Base64 string should be padded or not.
* Default is false.
* @return The Base64Url-encoded string.
*
* Note: If the value type is unsupported for this conversion, the method will throw an exception.
*/
@JvmOverloads
public fun toBase64Url(padding: Boolean = true): String {
public fun toBase64Url(padding: Boolean = false): String {
val encoder = if (padding) B64URL_ENCODER else B64URL_ENCODER.withoutPadding()

return when (this.value) {
Expand Down
29 changes: 28 additions & 1 deletion common/src/main/kotlin/web5/sdk/common/Json.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package web5.sdk.common

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

/**
Expand All @@ -21,6 +24,7 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule
* ```
*/
public object Json {

/**
* The Jackson object mapper instance, shared across the lib.
*
Expand All @@ -32,7 +36,10 @@ public object Json {
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)



private val objectWriter: ObjectWriter = jsonMapper.writer()
public val objectReader: ObjectReader = jsonMapper.reader()

/**
* Converts a kotlin object to a json string.
Expand All @@ -43,4 +50,24 @@ public object Json {
public fun stringify(obj: Any): String {
return objectWriter.writeValueAsString(obj)
}
}

/**
* Parse a json string into a kotlin object.
*
* @param T type of the object to parse.
* @param payload JSON string to parse
* @return parsed type T
*/
public inline fun <reified T> parse(payload: String): T {
return objectReader.readValue(payload, T::class.java)
}

/**
* Parse a JSON string into a Map.
*
* @return String parsed into a Map
*/
public fun String.toMap(): Map<String, Any> {
return jsonMapper.readValue(this)
}
}
2 changes: 1 addition & 1 deletion config/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ style:
UselessCallOnNotNull:
active: true
UtilityClassWithPublicConstructor:
active: true
active: false

TbdRuleset:
JvmOverloadsAnnotationRule:
Expand Down
2 changes: 2 additions & 0 deletions credentials/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ dependencies {
implementation(project(":dids"))
implementation(project(":common"))
implementation(project(":crypto"))
implementation(project(":jose"))


// Implementation
implementation(libs.comFasterXmlJacksonModuleKotlin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package web5.sdk.credentials

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ObjectNode
import com.networknt.schema.JsonSchema
import com.nfeld.jsonpathkt.JsonPath
import com.nfeld.jsonpathkt.extension.read
import com.nimbusds.jwt.JWTParser
import com.nimbusds.jwt.SignedJWT
import web5.sdk.common.Json
import web5.sdk.credentials.model.InputDescriptorMapping
import web5.sdk.credentials.model.InputDescriptorV2
import web5.sdk.credentials.model.PresentationDefinitionV2
import web5.sdk.credentials.model.PresentationDefinitionV2Validator
import web5.sdk.credentials.model.PresentationSubmission
import web5.sdk.credentials.model.PresentationSubmissionValidator
import web5.sdk.jose.JwtClaimsSetSerializer
import web5.sdk.jose.jwt.Jwt
import web5.sdk.jose.jwt.JwtClaimsSet
import java.util.UUID

/**
Expand Down Expand Up @@ -161,12 +164,17 @@ public object PresentationExchange {
vcJwtList: Iterable<String>,
presentationDefinition: PresentationDefinitionV2
): Map<InputDescriptorV2, List<String>> {
val vcJwtListWithNodes = vcJwtList.zip(vcJwtList.map { vcJwt ->
val vc = JWTParser.parse(vcJwt) as SignedJWT

JsonPath.parse(vc.payload.toString())
?: throw JsonPathParseException()
})
val jwtModule = SimpleModule().addSerializer(JwtClaimsSet::class.java, JwtClaimsSetSerializer())
Json.jsonMapper.registerModule(jwtModule)

val vcJwtListWithNodes = vcJwtList.zip(
vcJwtList.map { vcJwt ->
val vc = Jwt.decode(vcJwt)
val jsonString = Json.jsonMapper.writeValueAsString(vc.claims)
Json.jsonMapper.readTree(jsonString)
?: throw JsonPathParseException()
})
return presentationDefinition.inputDescriptors.associateWith { inputDescriptor ->
vcJwtListWithNodes.filter { (_, node) ->
vcSatisfiesInputDescriptor(node, inputDescriptor)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue
import com.nfeld.jsonpathkt.JsonPath
import com.nfeld.jsonpathkt.extension.read
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.JWTParser
import com.nimbusds.jwt.SignedJWT
import web5.sdk.credentials.util.JwtUtil
import web5.sdk.dids.Did
import web5.sdk.common.Json
import web5.sdk.dids.did.BearerDid
import web5.sdk.jose.jwt.Jwt
import web5.sdk.jose.jwt.JwtClaimsSet
import java.net.URI
import java.security.SignatureException
import java.util.Date
Expand Down Expand Up @@ -53,7 +52,7 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
* If the [assertionMethodId] is null, the function will attempt to use the first available verification method from
* the [did]. The result is a String in a JWT format.
*
* @param did The [Did] used to sign the credential.
* @param did The [BearerDid] used to sign the credential.
* @param assertionMethodId An optional identifier for the assertion method that will be used for verification of the
* produced signature.
* @return The JWT representing the signed verifiable credential.
Expand All @@ -64,15 +63,15 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
* ```
*/
@JvmOverloads
public fun sign(did: Did, assertionMethodId: String? = null): String {
val payload = JWTClaimsSet.Builder()
public fun sign(did: BearerDid, assertionMethodId: String? = null): String {
val payload = JwtClaimsSet.Builder()
.issuer(vcDataModel.issuer.toString())
.issueTime(vcDataModel.issuanceDate)
.issueTime(vcDataModel.issuanceDate.time)
.subject(vcDataModel.credentialSubject.id.toString())
.claim("vc", vcDataModel.toMap())
.misc("vc", vcDataModel.toMap())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is misc the more official name than claim?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

misc is the top level field where we shove everything that's not a reserved keyword in JWT. i've followed the implementation in web5-dart

https://github.com/TBD54566975/web5-dart/blob/main/packages/web5/lib/src/jwt/jwt_claims.dart#L59

@mistermoe is there a spec i can reference for neal / do we need to write one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the standard to use is public / private claim names, depending on whether the claims are registered in an IANA registry

https://datatracker.ietf.org/doc/html/rfc7519#section-4.2

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think claims is more apt than misc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

leaving it as misc for now, but this needs a discussion and a decision - TBD54566975/web5-spec#143

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed @decentralgabe . misc is just something i came up with to avoid having to use the terms private and public in code because those are reserved. i think @nitro-neal 's claim() suggestion is a great idea. can address in a subsequent PR

.build()

return JwtUtil.sign(did, assertionMethodId, payload)
return Jwt.sign(did, payload)
}

/**
Expand Down Expand Up @@ -188,7 +187,8 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
* ```
*/
public fun verify(vcJwt: String) {
JwtUtil.verify(vcJwt)
val decodedJwt = Jwt.decode(vcJwt)
decodedJwt.verify()
}

/**
Expand All @@ -203,15 +203,12 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
* ```
*/
public fun parseJwt(vcJwt: String): VerifiableCredential {
val jwt = JWTParser.parse(vcJwt) as SignedJWT
val jwtPayload = jwt.payload.toJSONObject()
val vcDataModelValue = jwtPayload.getOrElse("vc") {
val jwt = Jwt.decode(vcJwt)
val jwtPayload = jwt.claims
val vcDataModelValue = jwtPayload.misc["vc"] ?:
throw IllegalArgumentException("jwt payload missing vc property")
}

@Suppress("UNCHECKED_CAST") // only partially unchecked. can only safely cast to Map<*, *>
val vcDataModelMap = vcDataModelValue as? Map<String, Any>
?: throw IllegalArgumentException("expected vc property in JWT payload to be an object")
val vcDataModelMap = Json.parse<Map<String, Any>>(Json.stringify(vcDataModelValue))

val vcDataModel = VcDataModel.fromMap(vcDataModelMap)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ package web5.sdk.credentials
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.JWTParser
import com.nimbusds.jwt.SignedJWT
import web5.sdk.credentials.util.JwtUtil
import web5.sdk.dids.Did
import web5.sdk.common.Json
import web5.sdk.dids.did.BearerDid
import web5.sdk.jose.jwt.Jwt
import web5.sdk.jose.jwt.JwtClaimsSet
import java.net.URI
import java.security.SignatureException
import java.util.Date
Expand Down Expand Up @@ -37,17 +36,17 @@ public class VerifiablePresentation internal constructor(public val vpDataModel:
public val verifiableCredential: List<String>
get() = vpDataModel.toMap()["verifiableCredential"] as List<String>

public val holder: String
public val holder: String
get() = vpDataModel.holder.toString()

/**
* Sign a verifiable presentation using a specified decentralized identifier ([did]) with the private key that pairs
* Sign a verifiable presentation using a specified decentralized identifier ([bearerDid]) with the private key that pairs
* with the public key identified by [assertionMethodId].
*
* If the [assertionMethodId] is null, the function will attempt to use the first available verification method from
* the [did]. The result is a String in a JWT format.
* the [bearerDid]. The result is a String in a JWT format.
*
* @param did The [Did] used to sign the credential.
* @param bearerDid The [BearerDid] used to sign the credential.
* @param assertionMethodId An optional identifier for the assertion method that will be used for verification of the
* produced signature.
* @return The JWT representing the signed verifiable credential.
Expand All @@ -58,14 +57,14 @@ public class VerifiablePresentation internal constructor(public val vpDataModel:
* ```
*/
@JvmOverloads
public fun sign(did: Did, assertionMethodId: String? = null): String {
val payload = JWTClaimsSet.Builder()
.issuer(did.uri)
.issueTime(Date())
.claim("vp", vpDataModel.toMap())
public fun sign(bearerDid: BearerDid, assertionMethodId: String? = null): String {
val payload = JwtClaimsSet.Builder()
.issuer(bearerDid.uri)
.issueTime(Date().time)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is iat may need to change to seconds, currently this is milliseconds I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, and updated the ktdoc

.misc("vp", vpDataModel.toMap())
.build()

return JwtUtil.sign(did, assertionMethodId, payload)
return Jwt.sign(bearerDid, payload)
}

/**
Expand Down Expand Up @@ -153,7 +152,8 @@ public class VerifiablePresentation internal constructor(public val vpDataModel:
* ```
*/
public fun verify(vpJwt: String) {
JwtUtil.verify(vpJwt)
val decodedJwt = Jwt.decode(vpJwt)
decodedJwt.verify()

val vp = this.parseJwt(vpJwt)
vp.verifiableCredential.forEach {
Expand All @@ -177,15 +177,12 @@ public class VerifiablePresentation internal constructor(public val vpDataModel:
* ```
*/
public fun parseJwt(vpJwt: String): VerifiablePresentation {
val jwt = JWTParser.parse(vpJwt) as SignedJWT
val jwtPayload = jwt.payload.toJSONObject()
val vpDataModelValue = jwtPayload.getOrElse("vp") {
throw IllegalArgumentException("jwt payload missing vp property")
}
val jwt = Jwt.decode(vpJwt)
val jwtPayload = jwt.claims
val vpDataModelValue = jwtPayload.misc["vp"]
?: throw IllegalArgumentException("jwt payload missing vp property")

@Suppress("UNCHECKED_CAST") // only partially unchecked. can only safely cast to Map<*, *>
val vpDataModelMap = vpDataModelValue as? Map<String, Any>
?: throw IllegalArgumentException("expected vp property in JWT payload to be an object")
val vpDataModelMap = Json.parse<Map<String, Any>>(Json.stringify(vpDataModelValue))

val vpDataModel = VpDataModel.fromMap(vpDataModelMap)

Expand Down
Loading
Loading