Skip to content

Commit

Permalink
Fixes issue TBD54566975#176. Added VerifiableCredentialJwt to support…
Browse files Browse the repository at this point in the history
… verifyAndParse()
  • Loading branch information
grahnj committed Feb 3, 2024
1 parent 6ad5c2f commit 6beea9a
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ public object StatusListCredential {
val response: HttpResponse = this.get(url)
if (response.status.isSuccess()) {
val body = response.bodyAsText()
return VerifiableCredential.parseJwt(body)
return VerifiableCredentialJwt(body).verifyAndParse()
} else {
throw ClientRequestException(
response,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package web5.sdk.credentials

import com.danubetech.verifiablecredentials.CredentialSubject
import com.danubetech.verifiablecredentials.credentialstatus.CredentialStatus
import com.danubetech.verifiablecredentials.jwt.JwtVerifiableCredential
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.JsonNode
Expand All @@ -26,6 +27,55 @@ import java.util.UUID
*/
public typealias VcDataModel = com.danubetech.verifiablecredentials.VerifiableCredential

/**
* Wrapper class for a potentially unverified JWT [String]
* to ensure that verification occurs before parsing and avoid consumer confusion.
*
* The only mechanism to retrieve a [VerifiableCredential] from the wrapped String
* is by executing [verifyAndParse].
*
*/
public class VerifiableCredentialJwt(public val verifiableCredentialJwt: String) {

/**
* Verifies a raw JWT string to ensure that it is not from a bad
* actor before parsing it into a [VerifiableCredential].
*
* @throws [SignatureException] if verification fails.
*/
public fun verifyAndParse(): VerifiableCredential {
VerifiableCredential.verify(verifiableCredentialJwt)
return parseJwt()
}

/**
* Parses a JWT into a [VerifiableCredential] instance.
*
* @param vcJwt The verifiable credential JWT as a [String].
* @return A [VerifiableCredential] instance derived from the JWT.
*
* Example:
* ```
* val vc = VerifiableCredential.parseJwt(signedVcJwt)
* ```
*/
internal fun parseJwt(): VerifiableCredential {
val jwt = JWTParser.parse(verifiableCredentialJwt) as SignedJWT
val jwtPayload = jwt.payload.toJSONObject()
val vcDataModelValue = jwtPayload.getOrElse("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 vcDataModel = VcDataModel.fromMap(vcDataModelMap)

return VerifiableCredential(vcDataModel)
}
}

/**
* `VerifiableCredential` represents a digitally verifiable credential according to the
* [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/).
Expand Down Expand Up @@ -64,15 +114,15 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
* ```
*/
@JvmOverloads
public fun sign(did: Did, assertionMethodId: String? = null): String {
public fun sign(did: Did, assertionMethodId: String? = null): VerifiableCredentialJwt {
val payload = JWTClaimsSet.Builder()
.issuer(vcDataModel.issuer.toString())
.issueTime(vcDataModel.issuanceDate)
.subject(vcDataModel.credentialSubject.id.toString())
.claim("vc", vcDataModel.toMap())
.build()

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

/**
Expand Down Expand Up @@ -191,33 +241,6 @@ public class VerifiableCredential internal constructor(public val vcDataModel: V
JwtUtil.verify(vcJwt)
}

/**
* Parses a JWT into a [VerifiableCredential] instance.
*
* @param vcJwt The verifiable credential JWT as a [String].
* @return A [VerifiableCredential] instance derived from the JWT.
*
* Example:
* ```
* val vc = VerifiableCredential.parseJwt(signedVcJwt)
* ```
*/
public fun parseJwt(vcJwt: String): VerifiableCredential {
val jwt = JWTParser.parse(vcJwt) as SignedJWT
val jwtPayload = jwt.payload.toJSONObject()
val vcDataModelValue = jwtPayload.getOrElse("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 vcDataModel = VcDataModel.fromMap(vcDataModelMap)

return VerifiableCredential(vcDataModel)
}

/**
* Parses a JSON string into a [VerifiableCredential] instance.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }
}
Expand All @@ -82,7 +82,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }
}
Expand All @@ -99,7 +99,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }
}
Expand All @@ -116,7 +116,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }
}
Expand All @@ -135,7 +135,7 @@ class PresentationExchangeTest {
data = DateOfBirth(dateOfBirth = "1/1/1111")
)

val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt
assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd) }
}

Expand All @@ -152,15 +152,15 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "Data1")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "Address",
issuer = issuerDid.uri,
subject = holderDid.uri,
data = Address("abc street 123")
)
val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt2, vcJwt1), pd) }
}
Expand All @@ -178,7 +178,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt1), pd) }
}
Expand All @@ -196,7 +196,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt1), pd) }
}
Expand All @@ -215,7 +215,7 @@ class PresentationExchangeTest {
data = DateOfBirth(dateOfBirth = "1/1/1111")
)

val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "Address",
Expand All @@ -224,7 +224,7 @@ class PresentationExchangeTest {
data = Address(address = "123 abc street")
)

val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

assertDoesNotThrow { PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt2, vcJwt1), pd) }
}
Expand Down Expand Up @@ -256,7 +256,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertThrows<IllegalArgumentException> {
PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd)
Expand All @@ -281,7 +281,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertThrows<IllegalArgumentException> {
PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd)
Expand All @@ -304,7 +304,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "01-02-03")
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertThrows<IllegalArgumentException> {
PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd)
Expand All @@ -327,7 +327,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "01-02-03")
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertFailure {
PresentationExchange.satisfiesPresentationDefinition(listOf(vcJwt), pd)
Expand Down Expand Up @@ -368,15 +368,15 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "Data1")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "Address",
issuer = issuerDid.uri,
subject = holderDid.uri,
data = Address("abc street 123")
)
val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

val presentationSubmission = PresentationExchange.createPresentationFromCredentials(listOf(vcJwt2, vcJwt1), pd)

Expand All @@ -396,7 +396,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

assertFailure {
PresentationExchange.createPresentationFromCredentials(listOf(vcJwt), pd)
Expand All @@ -416,15 +416,15 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "11/11/2011")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "DateOfBirth",
issuer = issuerDid.uri,
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "12/12/2012")
)
val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

val presentationSubmission = PresentationExchange.createPresentationFromCredentials(listOf(vcJwt2, vcJwt1), pd)

Expand All @@ -448,7 +448,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt = vc.sign(issuerDid)
val vcJwt = vc.sign(issuerDid).verifiableCredentialJwt

val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt), pd)

Expand All @@ -469,15 +469,15 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "StreetCred",
issuer = issuerDid.uri,
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2), pd)

Expand All @@ -498,7 +498,7 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = StreetCredibility(localRespect = "high", legit = true)
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "StreetCred",
Expand All @@ -507,7 +507,7 @@ class PresentationExchangeTest {
data = StreetCredibility(localRespect = "high", legit = true)
)

val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

val vc3 = VerifiableCredential.create(
type = "DateOfBirth",
Expand All @@ -516,7 +516,7 @@ class PresentationExchangeTest {
data = DateOfBirth(dateOfBirth = "1-1-1111")
)

val vcJwt3 = vc3.sign(issuerDid)
val vcJwt3 = vc3.sign(issuerDid).verifiableCredentialJwt

val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2, vcJwt3), pd)

Expand All @@ -537,15 +537,15 @@ class PresentationExchangeTest {
subject = holderDid.uri,
data = DateOfBirthSSN(dateOfBirth = "1999-01-01", ssn = "456-123-123")
)
val vcJwt1 = vc1.sign(issuerDid)
val vcJwt1 = vc1.sign(issuerDid).verifiableCredentialJwt

val vc2 = VerifiableCredential.create(
type = "DateOfBirthSSN",
issuer = issuerDid.uri,
subject = holderDid.uri,
data = DateOfBirth(dateOfBirth = "1999-01-01")
)
val vcJwt2 = vc2.sign(issuerDid)
val vcJwt2 = vc2.sign(issuerDid).verifiableCredentialJwt

val selectedCreds = PresentationExchange.selectCredentials(listOf(vcJwt1, vcJwt2), pd)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ class StatusListCredentialTest {
addHandler { request ->
when (request.url.fullPath) {
"/credentials/status/3" -> {
val responseBody = slcJwt
val responseBody = slcJwt.verifiableCredentialJwt
respond(responseBody, headers = headersOf("Content-Type", "application/json"))
}

Expand Down
Loading

0 comments on commit 6beea9a

Please sign in to comment.