Skip to content

Commit

Permalink
refactor: update sd-jwt lib (#318)
Browse files Browse the repository at this point in the history
Co-authored-by: severinstampler <[email protected]>
  • Loading branch information
mikeplotean and severinstampler authored Jun 16, 2023
1 parent 367b5a5 commit eed3572
Show file tree
Hide file tree
Showing 19 changed files with 136 additions and 39 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ dependencies {
implementation("com.microsoft.azure:azure-client-authentication:1.7.14")
implementation("com.nimbusds:nimbus-jose-jwt:9.30.2")
implementation("com.nimbusds:oauth2-oidc-sdk:10.7")
implementation("id.walt:waltid-sd-jwt-jvm:1.2306071206.0")
implementation("id.walt:waltid-sd-jwt-jvm:1.2306160840.0")

implementation("org.bouncycastle:bcprov-jdk15to18:1.72")
implementation("org.bouncycastle:bcpkix-jdk15to18:1.72")
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/id/walt/common/OidcUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ object OidcUtil {
val scope = pm["scope"]!!
val authRequestJwt = pm["request"]!!

if (jwtService.verify(authRequestJwt)) {
if (jwtService.verify(authRequestJwt).verified) {
log.debug { "Successfully verified signature of JWT" }
} else {
throw IllegalArgumentException("Could not verify JWT $authRequestJwt")
Expand Down Expand Up @@ -163,7 +163,7 @@ object OidcUtil {

val jwt = jwtService.sign(did, payload)

jwtService.verify(jwt).let { if (!it) throw IllegalStateException("Generated JWK not valid") }
jwtService.verify(jwt).let { if (!it.verified) throw IllegalStateException("Generated JWK not valid") }

return jwt
}
Expand Down
49 changes: 39 additions & 10 deletions src/main/kotlin/id/walt/common/SerializationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import com.beust.klaxon.*
import id.walt.credentials.w3c.VerifiableCredential
import id.walt.credentials.w3c.toVerifiableCredential
import id.walt.model.VerificationMethod
import id.walt.sdjwt.SDMap
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject

@Target(AnnotationTarget.FIELD)
annotation class VCList
Expand All @@ -26,12 +29,19 @@ annotation class SingleVCObject
@Target(AnnotationTarget.FIELD)
annotation class JsonObjectField


@Target(AnnotationTarget.FIELD)
annotation class KotlinxJsonObjectField

@Target(AnnotationTarget.FIELD)
annotation class ListOrSingleValue

@Target(AnnotationTarget.FIELD)
annotation class DidVerificationRelationships

@Target(AnnotationTarget.FIELD)
annotation class SDMapProperty

class VcConverter(private val singleVC: Boolean, private val singleIfOne: Boolean, private val toVcObject: Boolean) :
Converter {
override fun canConvert(cls: Class<*>) = singleVC && cls == VerifiableCredential::class.java || cls == List::class.java
Expand Down Expand Up @@ -87,6 +97,19 @@ val jsonObjectFieldConverter = object : Converter {
}
}

val kotlinxJsonObjectFieldConverter = object : Converter {
override fun canConvert(cls: Class<*>) = cls == kotlinx.serialization.json.JsonObject::class.java

override fun fromJson(jv: JsonValue): Any? {
return jv.obj?.toJsonString()?.let { Json.parseToJsonElement(it) }?.jsonObject
}

override fun toJson(value: Any): String {
return (value as kotlinx.serialization.json.JsonObject).toString()
}

}

val listOrSingleValueConverter = object : Converter {
override fun canConvert(cls: Class<*>) = cls == List::class.java

Expand Down Expand Up @@ -126,6 +149,19 @@ val didVerificationRelationshipsConverter = object : Converter {
}
}

val sdMapConverter = object : Converter {
override fun canConvert(cls: Class<*>) = cls == SDMap::class.java

override fun fromJson(jv: JsonValue): Any? {
return jv.obj?.toJsonString()?.let { SDMap.fromJSON(it) }
}

override fun toJson(value: Any): String {
return (value as SDMap).toJSON().toString()
}

}

fun KlaxonWithConverters() = Klaxon()
.fieldConverter(VCList::class, VcConverter(singleVC = false, singleIfOne = false, toVcObject = false))
.fieldConverter(VCObjectList::class, VcConverter(singleVC = false, singleIfOne = false, toVcObject = true))
Expand All @@ -136,15 +172,8 @@ fun KlaxonWithConverters() = Klaxon()
.fieldConverter(ListOrSingleValue::class, listOrSingleValueConverter)
.fieldConverter(JsonObjectField::class, jsonObjectFieldConverter)
.fieldConverter(DidVerificationRelationships::class, didVerificationRelationshipsConverter)
.fieldConverter(SDMapProperty::class, sdMapConverter)
.fieldConverter(KotlinxJsonObjectField::class, kotlinxJsonObjectFieldConverter)

@Deprecated("Use KlaxonWithConverters()")
val KlaxonWithConverters = Klaxon()
.fieldConverter(VCList::class, VcConverter(singleVC = false, singleIfOne = false, toVcObject = false))
.fieldConverter(VCObjectList::class, VcConverter(singleVC = false, singleIfOne = false, toVcObject = true))
.fieldConverter(ListOrSingleVC::class, VcConverter(singleVC = false, singleIfOne = true, toVcObject = false))
.fieldConverter(ListOrSingleVCObject::class, VcConverter(singleVC = false, singleIfOne = true, toVcObject = true))
.fieldConverter(SingleVC::class, VcConverter(singleVC = true, singleIfOne = false, toVcObject = false))
.fieldConverter(SingleVCObject::class, VcConverter(singleVC = true, singleIfOne = false, toVcObject = true))
.fieldConverter(ListOrSingleValue::class, listOrSingleValueConverter)
.fieldConverter(JsonObjectField::class, jsonObjectFieldConverter)
.fieldConverter(DidVerificationRelationships::class, didVerificationRelationshipsConverter)
val KlaxonWithConverters = KlaxonWithConverters()
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ open class VerifiableCredential internal constructor(
open val challenge
get() = when (this.sdJwt) {
null -> this.proof?.nonce
else -> sdJwt!!.sdPayload.undisclosedPayload["nonce"]?.jsonPrimitive?.contentOrNull
else -> sdJwt!!.undisclosedPayload["nonce"]?.jsonPrimitive?.contentOrNull
}

fun toJsonObject() = buildJsonObject {
Expand Down Expand Up @@ -120,11 +120,11 @@ open class VerifiableCredential internal constructor(
fun isSDJwt(data: String) = SDJwt.isSDJwt(data)

private fun fromSdJwt(sdJwt: SDJwt): VerifiableCredential {
val resolvedObject = sdJwt.sdPayload.fullPayload
val resolvedObject = sdJwt.fullPayload
val claimKey = possibleClaimKeys.first { it in resolvedObject.keys }
return fromJsonObject(resolvedObject[claimKey]!!.jsonObject).apply {
this.sdJwt = sdJwt
this.selectiveDisclosure = sdJwt.sdPayload.sdMap[claimKey]?.children
this.selectiveDisclosure = sdJwt.sdMap[claimKey]?.children
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package id.walt.credentials.w3c

import id.walt.credentials.w3c.builder.AbstractW3CCredentialBuilder
import id.walt.credentials.w3c.builder.CredentialFactory
import id.walt.sdjwt.SDField
import id.walt.sdjwt.SDMap
import id.walt.sdjwt.SDMapBuilder
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

Expand Down Expand Up @@ -56,13 +56,11 @@ data class PresentableCredential(
) {
fun toJsonElement() =
if(verifiableCredential.sdJwt != null) {
val claimKey = VerifiableCredential.possibleClaimKeys.first { it in verifiableCredential.sdJwt!!.sdPayload.undisclosedPayload.keys }
val claimKey = VerifiableCredential.possibleClaimKeys.first { it in verifiableCredential.sdJwt!!.undisclosedPayload.keys }
val presentedJwt = if(discloseAll) {
verifiableCredential.sdJwt!!.present(discloseAll)
} else {
verifiableCredential.sdJwt!!.present(selectiveDisclosure?.let { mapOf(
claimKey to SDField(true, it)
)})
verifiableCredential.sdJwt!!.present(selectiveDisclosure?.let { SDMapBuilder().addField(claimKey, false, it).build() })
}
JsonPrimitive(presentedJwt.toString(formatForPresentation = true))
} else verifiableCredential.toJsonElement()
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/model/oidc/IDToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ data class IDToken(
if (KeyStoreService.getService().getKeyId(subject) == null) {
DidService.importKeys(subject)
}
return JwtService.getService().verify(jwt!!)
return JwtService.getService().verify(jwt!!).verified
}
return false
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/id/walt/model/oidc/SelfIssuedIDToken.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data class SelfIssuedIDToken(
if (KeyStoreService.getService().getKeyId(parsedToken.subject) == null) {
DidService.importKeys(parsedToken.subject)
}
return JwtService.getService().verify(jwt)
return JwtService.getService().verify(jwt).verified
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ open class EnterpriseWalletService : WaltIdService() {
log.debug { jwtToVerify.header }
log.debug { jwtToVerify.payload }

jwtService.verify(jwt).let { if (!it) throw IllegalStateException("Generated JWK not valid") }
jwtService.verify(jwt).let { if (!it.verified) throw IllegalStateException("Generated JWK not valid") }

log.debug { "AuthResponse JWT: $jwt" }
return jwt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ object UserWalletService {

log.debug { "Siop Response JWT:\n$jwt" }

jwtService.verify(jwt).let { if (!it) throw IllegalStateException("Generated JWK not valid") }
jwtService.verify(jwt).let { if (!it.verified) throw IllegalStateException("Generated JWK not valid") }

return jwt
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/id/walt/services/jwt/JwtService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package id.walt.services.jwt

import com.nimbusds.jose.jwk.OctetKeyPair
import id.walt.sdjwt.JWTCryptoProvider
import id.walt.sdjwt.JwtVerificationResult
import id.walt.servicematrix.ServiceProvider
import id.walt.services.WaltIdService
import kotlinx.serialization.json.JsonObject
Expand Down Expand Up @@ -35,7 +36,7 @@ open class JwtService : WaltIdService(), JWTCryptoProvider {
return sign(keyID, payload.toString())
}

override fun verify(token: String): Boolean = implementation.verify(token)
override fun verify(token: String): JwtVerificationResult = implementation.verify(token)

open fun parseClaims(token: String): MutableMap<String, Any>? = implementation.parseClaims(token)

Expand Down
5 changes: 3 additions & 2 deletions src/main/kotlin/id/walt/services/jwt/WaltIdJwtService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.nimbusds.jwt.JWTClaimsSet
import com.nimbusds.jwt.SignedJWT
import id.walt.crypto.*
import id.walt.model.DidUrl
import id.walt.sdjwt.JwtVerificationResult
import id.walt.services.did.DidService
import id.walt.services.key.KeyService
import mu.KotlinLogging
Expand Down Expand Up @@ -147,7 +148,7 @@ open class WaltIdJwtService : JwtService() {
return serializedSignedJwt
}

override fun verify(token: String): Boolean {
override fun verify(token: String): JwtVerificationResult {
log.debug { "Verifying token: $token" }
val jwt = SignedJWT.parse(token)
val issuer = jwt.jwtClaimsSet.issuer
Expand Down Expand Up @@ -183,7 +184,7 @@ open class WaltIdJwtService : JwtService() {
}

log.debug { "JWT verified returned: $res" }
return res
return JwtVerificationResult(res)
}

override fun parseClaims(token: String): MutableMap<String, Any>? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ open class WaltIdJwtCredentialService : JwtCredentialService() {

override fun verifyVc(vc: String): Boolean {
log.debug { "Verifying vc: $vc" }
return SDJwt.parse(vc).verify(jwtService)
return SDJwt.parse(vc).verify(jwtService).verified
}

override fun verifyVp(vp: String): Boolean =
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/id/walt/signatory/Signatory.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package id.walt.signatory

import com.beust.klaxon.Json
import id.walt.common.SDMapProperty
import id.walt.credentials.w3c.VerifiableCredential
import id.walt.credentials.w3c.W3CIssuer
import id.walt.credentials.w3c.builder.AbstractW3CCredentialBuilder
Expand Down Expand Up @@ -48,7 +49,7 @@ data class ProofConfig(
@Json(serializeNull = false) val statusType: CredentialStatus.Types? = null,
@Json(serializeNull = false) val statusPurpose: String = "revocation",
@Json(serializeNull = false) val credentialsEndpoint: String? = null,
@Json(serializeNull = false) val selectiveDisclosure: SDMap? = null
@Json(serializeNull = false) @SDMapProperty val selectiveDisclosure: SDMap? = null
)

data class SignatoryConfig(
Expand Down
6 changes: 4 additions & 2 deletions src/main/kotlin/id/walt/signatory/rest/SignatoryController.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package id.walt.signatory.rest

import id.walt.common.KlaxonWithConverters
import id.walt.common.KotlinxJsonObjectField
import id.walt.credentials.w3c.JsonConverter
import id.walt.credentials.w3c.VerifiableCredential
import id.walt.credentials.w3c.builder.W3CCredentialBuilder
Expand All @@ -20,12 +21,13 @@ import io.javalin.http.ContentType
import io.javalin.http.Context
import io.javalin.http.HttpCode
import io.javalin.plugin.openapi.dsl.document
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject

data class IssueCredentialRequest(
val templateId: String?,
val config: ProofConfig,
val credentialData: Map<String, Any>? = null
@KotlinxJsonObjectField val credentialData: JsonObject? = null
)

object SignatoryController {
Expand Down Expand Up @@ -75,7 +77,7 @@ object SignatoryController {
}.pathParam<String>("id").result<String>("200")

fun issueCredential(ctx: Context) {
val req = ctx.bodyAsClass<IssueCredentialRequest>()
val req = KlaxonWithConverters().parse<IssueCredentialRequest>(ctx.body()) ?: throw BadRequestResponse("Cannot parse IssueCredentialRequest body")
if (req.templateId != null && !signatory.hasTemplateId(req.templateId)) {
throw BadRequestResponse("Template with supplied id does not exist.")
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/kotlin/id/walt/services/jwt/JwtServiceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class JwtServiceTest : AnnotationSpec() {
"https://walt.id" shouldBe signedJwt.jwtClaimsSet.claims["iss"]

val res1 = jwtService.verify(jwt)
res1 shouldBe true
res1.verified shouldBe true
}

@Test
Expand All @@ -53,7 +53,7 @@ class JwtServiceTest : AnnotationSpec() {
"https://walt.id" shouldBe signedJwt.jwtClaimsSet.claims["iss"]

val res1 = jwtService.verify(jwt)
res1 shouldBe true
res1.verified shouldBe true
}

@Test
Expand All @@ -80,7 +80,7 @@ class JwtServiceTest : AnnotationSpec() {
"https://self-issued.me" shouldBe jwt.jwtClaimsSet.claims["iss"]
thumbprint shouldBe jwt.jwtClaimsSet.claims["sub"]

jwtService.verify(jwtStr) shouldBe true
jwtService.verify(jwtStr).verified shouldBe true
}

}
2 changes: 1 addition & 1 deletion src/test/kotlin/id/walt/services/oidc/OIDC4VCTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ class OIDC4VCTest : AnnotationSpec() {
fun testCrosswordCyberSecurityDidProof() {
val proof =
JwtProof("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImRpZDpqd2s6ZXlKNUlqb2lNako0Umw5NVRsUjZkVzVSVEU1dmExWXlNMTlwTFhGS2EwOU5NVlptVUdZNFRFeFhOM1JHZDBkcWF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJbVJKTmtoMGVrMDJXSEZZUkhCd1MzTkJiR0kzWDNkd1FuWlpjRTVZV0c5UldHcHFjRUpGVURWaE5tOGlMQ0poYkdjaU9pSkZVekkxTmlJc0ltdDBlU0k2SWtWREluMCJ9.eyJleHAiOjE2NjYwMjM0NjYsImp0aSI6IjVVT0xnR29keXZ1R2dZVTZcLzF2Tk9kd0pVUUpkM085VWlCTHg1Z2VSSlpBPSIsImlzcyI6ImRpZDpqd2s6ZXlKNUlqb2lNako0Umw5NVRsUjZkVzVSVEU1dmExWXlNMTlwTFhGS2EwOU5NVlptVUdZNFRFeFhOM1JHZDBkcWF5SXNJbU55ZGlJNklsQXRNalUySWl3aWVDSTZJbVJKTmtoMGVrMDJXSEZZUkhCd1MzTkJiR0kzWDNkd1FuWlpjRTVZV0c5UldHcHFjRUpGVURWaE5tOGlMQ0poYkdjaU9pSkZVekkxTmlJc0ltdDBlU0k2SWtWREluMCIsImF1ZCI6Imh0dHBzOlwvXC9pc3N1ZXIud2FsdC10ZXN0LmNsb3VkXC9pc3N1ZXItYXBpXC9vaWRjXC8iLCJpYXQiOjE2NjYwMjM0MDZ9.oZd5hfs1iLQMiaL3VUvxG93B5CjKsVmH45WPdXGeeSnqBm5QF7eHNlakZ4u8KGlrq0gqXLc5lXP-u0rMzbNZkw")
JwtService.getService().verify(proof.jwt) shouldBe true
JwtService.getService().verify(proof.jwt).verified shouldBe true
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ class WaltIdJwtCredentialServiceTest : AnnotationSpec() {

val parsedSdJwt = SDJwt.parse(issuedVID)
parsedSdJwt.disclosures shouldHaveSize 3
parsedSdJwt.sdPayload.sDisclosures.map { sd -> sd.key } shouldContainAll setOf("credentialSubject", "firstName", "dateOfBirth")
parsedSdJwt.disclosureObjects.map { sd -> sd.key } shouldContainAll setOf("credentialSubject", "firstName", "dateOfBirth")

Auditor.getService().verify(issuedVID, listOf(SignaturePolicy())).result shouldBe true

Expand Down
Loading

0 comments on commit eed3572

Please sign in to comment.