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

Update VerifiableCredential API #176

Closed
andresuribe87 opened this issue Dec 19, 2023 · 1 comment · May be fixed by #216
Closed

Update VerifiableCredential API #176

andresuribe87 opened this issue Dec 19, 2023 · 1 comment · May be fixed by #216

Comments

@andresuribe87
Copy link
Contributor

Current State

The API for verifiable credential looks as follows:

public class VerifiableCredential internal constructor(public val vcDataModel: VcDataModel) {
  @JvmOverloads
  public fun sign(did: Did, assertionMethodId: String? = null): String {}

  public companion object {
    @JvmOverloads
    public fun <T> create(
      type: String,
      issuer: String,
      subject: String,
      data: T,
      credentialStatus: CredentialStatus? = null
    ): VerifiableCredential {}
  
    public fun verify(vcJwt: String) {}
    
    public fun parseJwt(vcJwt: String): VerifiableCredential {}

    public fun fromJson(vcJson: String): VerifiableCredential {}
  }
}

The following are the relevant types for consumers of the library:

  • VerifiableCredential to represent the data model that's defined in the spec in https://www.w3.org/TR/vc-data-model/). But (and this is important), it is not secured. That is, there is no way for a consumer who has an object of this type to know whether the contents have been integrity checked.
  • String, which represents a verifiable credential secured as a JWT.

Use Cases

Typical use cases of this API depend on what role the implementer is aiming to fulfill. More details are shown below.

Use case 1: Verifier

Assuming a verifier is being implemented, a consumer of the API will be expected to have something similar to the following snippet of code

val vcJwt = "some VC from the interwebs"

try {
  VerifiableCredential.verify(vcJwt)
} catch (e: Exception) {
  // this credential cannot be trusted!
}
val vc = VerifiableCredential.parseJwt(vcJwt)

myCustomBusinessLogic(vc)

Use Case 2: Issuer

Assuming an issuer is being implemented, a consumer of the API will be expected to have something similar to the following snippet of code

data class DateOfBirth(val dateOfBirth: String)

val vc = VerifiableCredential.create(
  type = "DateOfBirth",
  issuer = issuerDid.uri,
  subject = holderDid.uri,
  data = DateOfBirth(dateOfBirth = "01-02-03")
)

val vcJwt = vc.sign(issuerDid)

shipTheVcJwtSomewhere(vcJwt)

Use Case 3: Holder

Holders are a bit of a special case, because they will actually be issuing VCs (e.g. a self-issued credentials), verifying VCs, and displaying the content from a VC to the user, so they can select which VCs to send over. The code for issuing and verifying use cases is similar to the one above. For display purposes, they'll do something similar to the following

// Let `table` be a handle towards a view which will be rendering properties of the credentials. 
val table: VCTable

// Assume that the vcJwt is stored in a credentialStore.
val vcs: Iterable<String> = credentialStore.getAllVerifiableCredentials()

vcs.map { VerifiableCredential.parseJwt(it) }.forEach {
  it.vcDataModel.credentialSubject.claims.forEach { property, value -> table.addRow(property, value) }
}

Problem

For use case 1 above (the issuer), the current API facilitates doing the following.

val vcJwt = "super bad guy faking bad stuff jwt"
val vc = VerifiableCredential.parseJwt(vcJwt)

This can have catastrophic consequences because the content of the VC might have been tampered! Consumers of the API might assume that parseJwt does signature verification and validation of the data within the credential. Can we do better?

Proposed new API

No footguns can be achieved by slightly tweaking the API, and introducing new types. The following API surfaces simplifies and makes it much harde to misuse.

public class VerifiableCredentialJWT(vcJwt: String) {
  public fun verifyAndParse(): VerifiableCredential {}
}

public class VerifiableCredential internal constructor(public val vcDataModel: VcDataModel) {
  @JvmOverloads
  public fun sign(did: Did, assertionMethodId: String? = null): VerifiableCredentialJWT {}

  public companion object {
    @JvmOverloads
    public fun <T> create(
      type: String,
      issuer: String,
      subject: String,
      data: T,
      credentialStatus: CredentialStatus? = null
    ): VerifiableCredential {}

    public fun fromJson(vcJson: String): VerifiableCredential {}
  }
}

The changes include:

  • Adding a VerifiableCredentialJWT class with the method verifyAndParse.
  • Removing the VerifiableCredential.parseJwt method.
  • Changing the return type of VerifiableCredential.sign so that it's a VerifiableCredentialJWT.

With the API above, the use case 1 then becomes:

val vcJwt = VerifiableCredentialJWT("some VC from the interwebs")
try {
  val vc = vcJwt.verify()
} catch (e: Exception) {
  // dragons be here
}
myCustomBusinessLogic(vc)

It's hard to misuse, because there is only one mechanism for going from a vcJwt -> VerifiableCredential.

There exists a tradeoff for the holder use case, because verification will be performed every time after retrieving from the credentialStore. I argue that this is ok, and it's really up to the implementer to decide how and what they're going to store. For example, in ssi-service (which acts as a holder) stores a number of things.

Future Work

A whole separate conversation to be had involves removing third party packages from the API. In this case, the type com.danubetech.verifiablecredentials.VerifiableCredential is currently public. We should strive to make it private, but it's out of scope for this issue.

Feedback is welcome!

@jiyoonie9
Copy link
Contributor

this should be addressed by an upcoming change to VerifiableCredential API surface change to be proposed in https://github.com/TBD54566975/web5-spec

@jiyoonie9 jiyoonie9 closed this as not planned Won't fix, can't repro, duplicate, stale Feb 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants